面向对象之原型
object-oriented 面向对象的设计, 不同于其他语言, js 中的面向对象没有类的概念, 因此, 其对象也有些特殊.
所谓对象就是无序属性的集合, 其属性可以包含基本值, 对象, 函数. 也就是说对象是一组没有特定顺序的值的集合; 对象的每个属性或方法都有自己名字, 名字映射到一个值(值可以是数据或者函数)
每个对象是基于引用类型创建的, 是某个引用类型的实例. 新对象就是 new 操作符后加一个构造函数创建的
1 理解对象
这个就没什么必要, 对象就是无序属性的集合, OK 了
2 创建对象
原始方式: 传统的方式, 只能单次创建, 不能重复利用, 代码重复率太高, 效率低
工厂模式: 抽象了具体创建对象的过程, 用函数来封装以特定接口创建对象的细节; 虽然解决创建多个相似对象的问题, 但是没有解决对象识别即怎么知道一个对象的类型问题
构造函数: 构造函数可以创建特定类型的对象比如 object,array 等等, 也可以创建自定义的构造函数, 从而定义自定义对象的类型和方法
// 普通的创建 只能单次创建, 不能重复使用, 大量重复代码, 不合理
var person1={
name:"double",
age:24,
sex:"man"
}
console.log(person1.name) //double
// 工厂模式 抽取了创建具体对象的过程
function createPerson(name,age,sex){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.sex=sex;
obj.sayName=function(){
console.log(this.name)
}
return obj; // 工厂模式下必须有 return
}
var person1=createPerson("double",23,"man")
var person2=createPerson("single",34,"woman")
console.log(person1.name) //double
// 构造函数模式 new + 构造函数
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
this.sayName=function(){
console.log(this.name);
}
//return this 可省略
}
var person1=new Person("double",34,"man")
var person2=new Person("single",32,"woman")
console.log(person1.name) //double
构造函数创建对象的不同
1, 没有显示的创建对象 2, 直接将属性和方法赋给 this 对象 3, 没有 return 语句
注意: 构造函数应该以大写字符开头, 普通函数以小写字符开头.
创建对象的过程
1, 创建一个新对象; 2, 将构造函数的作用域赋予给新对象; 3, 执行构造函数中的代码; 4, 返回新对象
对象的 constructor 属性, 指向对象的构造函数
console.log(person1.constructor == Person) //true // 对象都有一个 constructor(构造函数)属性, true 说明是指向 Person
console.log(person2.constructor == Person)
// 检测类型 instanceof 创建的对象既是 Object 又是 Person 的实例
console.log(person1 instanceof Object) //true
console.log(person1 instanceof Person)
console.log(person2 instanceof Object)
console.log(person2 instanceof Person)
关于构造函数的补充构造函数与函数的区别在于调用的方式不同. 任何函数只要通过 new 操作符调用就是构造函数
构造函数与普通函数
// 构造函数
var person3=new Person("nothing",35,"woman")
console.log(person3.age) //35
// 普通函数
Person("world",34,"man")
console.log(window.age) //34 直接调用, 那就跑到全局作用域上, 添加到 window
// 在另一个对象的作用域调用
var obj=new Object()
Person.call(obj,"double",45,"man")
console.log(obj.name) //double
构造函数的问题:
每个方法都要在每个实例上重新创建一遍; js 中函数本身就是对象, 所以每个实例上的每个新的方法, 就是新的对象; 不同实例上的同名函数是不相等的
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
this.sayName=function(){
return this.name
}
}
var person1=new Person("double",34,"man")
var person2=new Person("single",45,"woman")
console.log(person1.sayName==person2.sayName) //false 不是相同的函数, 而是分别创建了
console.log(person1.name.prototype==person2.name.prototype) //true 指向同一原型属性
console.log(person1.sayName.prototype==person2.sayName.prototype) //false 指向不同原型属性
// 创建两个完成相同任务的 Function 实例确实没必要, 况且有 Function 存在, 不需要将函数绑定到特定对象上去
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
this.sayName=sayName;
}
function sayName(){ // 提出来是可以的, person1 和 person2 指向全局作用域上的定义的同一函数, 一旦函数的方法多了就麻烦啦
return this.name
}
// 一句话结束: 原先是创建对象的方法来创建函数, 每个 person 中都包含不同 Function 实例, 会导致不同的作用域链和标识符解析; 现在是声明函数的方法创建函数
原型模式
每个引用类型 (Array Object Function) 都有 prototype(原型) 属性, 是个指针, 指向一个对象, 这个对象就是包含可以由特定类型的所有实例共享的属性和方法(即原型对象); 所有的引用类型都在其构造函数的原型上定义了方法, Array.prototype 可以找到 sort 方法, 在 String.prototype 可以找到 substring 方法;
好处: 让所有对象实例共享它包含的属性和方法
function Person(){
}
Person.prototype.name="double"
Person.prototype.age=23
Person.prototype.sex="man"
Person.prototype.sayName=function(){
console.log(this.name)
}
var person1=new Person()
person1.sayName() //double
var person2=new Person()
person2.sayName() //double
console.log(person1.sayName==person2.sayName) //true
1, 理解原型模式
prototype 属性: 每一个新创建的函数, 都有一个 prototype 属性, 说过了, 是个指针, 指向它的原型对象(Person.prototype);
constructor 属性: 所有的原型对象有个 constructor 属性, 也是个指针, 指向 prototype 所在的函数. 即 Person.prototype.constructor 指向的就是 Person 这个构造函数.
_proto_属性: 创建构造函数后, 其原型对象默认只会取得 constructor 属性, 其他方法会由 object 继承. 当调用构造函数创建一个新实例后, 该实例内部有个_proto_属性, 也是指针, 指向原型对象
注意: 这个属性对脚本是不可见的, 称为隐式属性; 这个连接存在于实例和原型对象之间, 不是存在实例与构造函数之间, 来看此图就知道了
isPrototypeOf() 方法: 这是原型对象的一个方法, 用来判断某个实例与原型对象是否存在_proto_联系
console.log(Person.prototype.isPrototypeOf(person1)) //true
console.log(Person.prototype.isPrototypeOf(person2)) //true
Object.getPrototypeOf() 方法: ES5 新增的方法, 返回原型对象
console.log(Object.getPrototypeOf(person1) == Person.prototype) //true
console.log(Object.getPrototypeOf(person1).name) //double
注意: 每当代码读取某个对象的某个属性, 都会执行一次搜索. 首先搜索该实例, 没有就搜索原型对象, 再没有就搜索 Object 原型对象; 一旦找到就返回, 不会继续搜索, 也就是说相同名字的属性就会被遮蔽, 而不是替代或者消失, 是遮蔽. 当删除属性时就会继续查找
hasOwnePrototype() 方法: 用来检测一个属性是存在实例中还是原型中(属性存在实例中返回 true)
console.log(person1.hasOwnProperty("name")) //false 根据上面的则是在原型中的
console.log(person1.hasOwnProperty("age")) //false
2, 原型和 in 操作符
in 操作符可以用两种方式, 一个是单独使用, 一个是 for in 循环中, 来吧
单独使用: 通过对象能够访问特定属性, 不管存在于实例还是原型中都返回 true
function Person(){
}
Person.prototype.name="double"
Person.prototype.age=23
Person.prototype.sex="man"
var person1=new Person()
var person2=new Person()
console.log(person1.hasOwnProperty("name")) //false 存在原型中
console.log("name" in person1) //true
person1.name="single"
console.log(person1.hasOwnProperty("name")) //true 存在实例中, 这里实例的 name 只是遮盖了原型中的 name
console.log("name" in person1) //true
// 可以通过 Object.hasOwnProperty()和操作符 in 来判断到底是存在于实例还是原型中
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object)
}
console.log(hasPrototypeProperty(person1,"name")) //false 说明存在实例中
console.log(hasPrototypeProperty(person2,"name")) //true 说明存在原型中
for in 使用: 返回的是所有能够通过对象访问的, 可枚举的 (enmuerated) 属性, 既包括实例中又包括原型中, 遮蔽的原型中 (不可枚举的) 的实例属性也会被循环返回; 但是这不是我们想要的结果, 我们只想要实例中可枚举的属性, 可以用 ES5 中 Object.keys()的方法: 接受一个对象作为参数, 返回一个包含所有可枚举属性的字符串数组
function Person(){
}
Person.prototype.name="double"
Person.prototype.age=23
Person.prototype.sex="man"
Person.prototype.sayName=function(){
return this.name
}
var person1=new Person()
var person2=new Person()
var keys=Object.keys(Person.prototype)
console.log(keys) // 返回的是字符串数组, 所有可枚举的属性["name","age","sex","sayName"]
var keyss=Object.getOwnPropertyNames(Person.prototype)
console.log(keyss) // 返回的是所有实例属性, 含有不可枚举的属性["constructor","name","age","sex","sayName"]
所以呢, 直接用字面量原型吧, 简洁明, 大方得体; 注意了: 因为将原型设置为字面量形式, 所以呢, 它现在变为一个新对象了, 结果是相同的, 但是呢, 这里的 constructor 不再指向 Person 了
为啥呢?
每创建一个对象, 此对象就会有他的原型对象(原型对象会自动得到 constructor), 这里重写了原型对象, 所以呢 constructor 就变成新原型对象的 constructor 属性, 指向的是 Object 构造函数
function Person(){
}
Person.prototype={ 字面量表示
constructor:Person, 当 constructor very important 时, 那就作为返回值带上呗
name : "double",
age : 12,
sex : "woman",
sayName : function(){
return this.name
}
}
var person1=new Person()
console.log(person1.name) //double
3, 原型的动态性
正如前面所说的, 当查找某一个属性的时候, 会进行层层的搜索, 首先在实例层中, 然后在原型层, 再次在 Object 中.
注意: 原型的查找是一次搜索的, 对原型的任意修改都会立即从实例上反映出来(即使是先创建实例后修改原型)
var person1=new Person()
Person.prototype.sayName=function(){
alert(this.age)
}
person1.sayName() //12
所以呢, 可以通过对原型添加, 修改某些属性达到效果, 但是当实例创建了, 重写原型 gg 了, 因为实例和原型间的_prototy_是个指针, 不是副本, 重写原型就切断了原先的联系, 会报错的.
基于前面都是自定义对象, 那么对于原生的引用类型呢, 是不是可以对原型自定义的添加属性呢?
当然可以啦, 但是不推荐做, 原因嘛, 你懂的.
// 为 String 原型添加个方法
String.prototype.startWidth=function(text){
return this.indexOf(text)==0
}
var message="Hello world"
console.log(message.startWidth("Hello")) //true
4, 原型模式的问题
从上面可以很容易的看出来, 共享性是原型模式的最大特色, 但是它是一把双刃剑, 对于函数方法十分有效, 游刃有余; 可是对于一些基本属性, 就没办法相构造函数那样解决(当然我们可以在后续添加, 遮盖原型中的属性); 最重要的是对于包含引用类型值得属性就是个大麻烦, 比如数组, 来看例子吧!
function Person(){
}
Person.prototype={
constructor:Person,
name:"double",
age:23,
sex:"woman",
friends:["single","mike"]
}
var person1=new Person()
var person2=new Person()
person1.friends.push("tom","jack")
console.log(person1.friends==person2.friends) //true 因为被共享了
所以各位, 这种原型模式你们会用吗?
那么有什么好的办法呢? 当然是有的, 来瞧一瞧看一看, 组合使用构造函数模式和原型模式就可以了(也是使用的最多的)
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
}
Person.prototype={
constructor:Person,
sayName:function(){
console.log(this.name)
}
}
var person1=new Person("double",23,"man")
var person2=new Person("single",34,"woman")
console.lgo(person11.name===person2.name) //false 基本值不一样
console.log(person1.sayName===person2.sayName) //true 方法一样
有些奇怪吗? 确实, 为啥好端端的函数时, 将原型和构造函数分开, 来吧, 让封装函数让你享受封装的魅力所在. 我们称之为动态原型模式(也就是比较完美了)
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
if(typeof this.sayName != "function"){ // 当 sayName 方法不存在时, 添加到原型中, 初次调用时执行, 此后原型已经初始化了, 后续更改也会在实例中实时反映的
Person.prototype.sayName=function(){
alert(this.name)
};
}
}
var friends=new Person("double",34,"man")
friends.sayName() //double
于此, 原型模式讲的也就差不多了, 补充一下, 事实上还有原型链中还有 Object, 所有函数 (构造和普通函数) 默认的原型都是 Object 的实例, 所以呢, 默认原型都会包含一个内部指针_proto_, 指向 Object.prototype 所以呢, 自定义类型都会继承 toString,valueOf 默认方法的根本原因. 结合上面, 看一下这个原型链的图吧
还有两种模式, 不常用, 了解一下吧
寄生构造函数模式: 函数仅仅是封装创建对象的代码, 然后返回新创建的对象
返回对象与构造函数与构造函数的原型属性没关系, 说白了, 就是个普通的新对象
在特殊的情况下为对象创建构造函数
function Person(name,age,sex){
var o=new Object();
o.name=name;
o.age=age;
o.sex=sex;
o.sayName=function(){
alert(this.name)
};
return o
}
var person=new Person("double",34,"man") // 只比工厂模式多个 new
person.sayName()
稳妥构造函数模式
没有公共属性, 方法也不引用 this 对象
在一些安全的环境下 (禁用 this 和 new) 或者在防止数据被其他应用程序改动时使用
function Person(name,age,sex){
var o=new Object()
o.sayName=function(){
alert(name)
}
return o
}
var friend=Person("double",34,"man")
friend.sayName() //true
好吧, 写了这么多, 关注一下呗, 后续再更继承的知识如果你关注我我就......(你懂的)
来源: https://www.cnblogs.com/iDouble/p/8401058.html