原型概念:
参考连接:
https://github.com/mqyqingfeng/Blog/issues/2
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
关键是弄清楚以下三个概念的关系:
构造函数 constructor function
原型对象 prototype object
实例(对象)instance object
几个容易混淆的方法:
-
hasOwnProperty() 用来判断一个属性是存在于实例中还是原型中,只有存在于实例中时才会返回true 使用方法: person1.hasOwnProperty(“name”);
- in 操作符
在通过对象能够访问属性时返回true,无论该属性位于实例还是原型中使用方法: “name” in person1
可以结合上面两个来检测位于原型中的属性:Function hasPrototypeProperty(object, name){ Return (!object.hasOwnProperty(name)) && (name in object); } -
for in 此循环返回的是所有能够通过对象访问的,可枚举的(enumerated属性)的属性,既包含原型中也包含实例中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回,因为根据规定,所以开放人员定义的属性都是可枚举的。
-
Object.keys(object) 返回包含实例对象中所有可枚举属性的字符串数组
- Object.getOwnPropertyNames(object) 获得所有实例属性,无论它是否可枚举
创建对象的几种模式:
- 工厂模式
即定义一个函数,通过调用函数创建对象Function createPerson(name, age, job){ Var o = new Object(); o.name = name; o.age = age; o.job = job; return o; } Var person1 = createPerson(“ste”, 29, “IT”);
问题是无法判定对象的具体类型
- 构造函数模式
定义构造函数,通过构造函数new来生成对象Function Person(name, age, job){ This.name = name; This.age = age; This.job = job; }Var person1 = new Person(“ste”, 28, “IT”); 缺点是,每个对象都生成一个新的实例,会导致不同实例的同名函数时不相等的。
- 原型模式
~~~ Function Person(){} Person.prototype.name = “ste”; Person.prototype.age = 25; Person.prototype.job = “IT”;
Var person1 = new Person();
缺点是,所有的实例都分享共同的属性和方法,虽然可以通过在实例中覆盖原型属性来定义自己的属性值,但是对于包含引用类型值的属性来说,两者指向同一个对象。
4. 组合使用原型模式和构造函数模式
构造函数用于定义实例属性,原型用于定义方法和共享属性
Function Person(name, age, job){ This.name = name; This.age = age; This.job = job; This.friends = [“s”, “t”]; } Person.prototype.constructor = Person; Person.prototype.sayName = function(){alert(this.name);};
Var person1 = new Person(“ste”, 29, “IT”);
5. 动态原型模式
把所有信息都封装在了构造函数中,通过在构造函数中初始化原型(仅在必要情况下),又保持同时使用构造函数和原型的优点。其本质思想和前面的组合模式相同,只是实现方式的改变。
Function Person(name, age, job){ This.name = name; This.age = age; This.job = job;
//method
If( typeof this.sayName != “function” ){
Person.prototype.sayName = function(){ alert(this.name); }; } }
Var person1 = new Person(“ste”, 29, “IT”);
6. 寄生构造函数模式
以上几种方式都不适用时可以使用这种方式,它是针对特殊情况下的方法。
它的基本思想是,创建一个函数,这个函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。
它和工厂模式及构造函数模式都有几分的相似性,可以看作两者的结合。
Function Person(name, age, job){ Var o = new Object(); o.name = name; o.age = age; o.job = job;
return o; } Var person1 = new Person(“ste”, 29, “IT”); ~~~ 这种方式要注意的是,构造函数在不返回值的情况下,默认会返回新对象实例,这也是为什么构造函数模式里面没有return语句的原因。 但是这种方式通过在函数末尾添加return语句,重写了调用构造函数时返回的值。 因此,这种方式返回的对象与构造函数的原型属性之间没有关系,构造函数返回的对象与在构造函数外部创建的对象没有什么不同,因此不可以通过instanceof来确定对象类型。
使用的一个例子: 打算创建一个具有额外方法的特殊数组,但又不能直接修改Array构造函数,就可以使用这种方式:
Function specialArray(){
Var values = new Array();
Values.push.apply(values, arguments);
Values.toPipedString = function(){ return this.join(“|”); };
Return values;
}
Var cls = new specialArray(“red”, “blue”, “green”);
cls.toPipedString(); //”red|blue|green”
- 稳妥构造函数模式
稳妥对象(durable objects),指没有公共属性,而且其方法不引用this的对象。最适合在一些安全环境(这些环境中禁止使用this and new),或者在防止数据被其他应用程序改动时使用。
此模式和寄生构造函数模式很像,有两点区别:
第一, 新创建对象的实例方法不使用this;第二,不是有new调用构造函数。Function Person(name, age, job){ Var o = new Object(); //可以创建私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; return o; } Var person1 = new Person(“ste”, 29, “IT”);注意,上面的例子中,除了sayName()方法,没有任何方法可以访问其数据成员。即使其他代码会给这个对象添加方法或数据成员,但也不可能有别的方法访问传入到构造函数的原始数据。
继承的几种实现:
ECMAScript只支持实现继承,通过原型链来实现。
- 原型链实现继承
subType .prototype = new superType(); 即通过创建要被继承的类(类似java中的父类)的实例,并将实例赋给subType.prototype来实现。
确定原型和实例的关系两种方式:
第一,instanceof操作符
Instance instanceof Object;
第二,isPrototypeOf()方法
Object.prototype.isPrototypeOf(instance);
这种方式有一些缺点,其中之一就是因其共享特性引起的。
- 借用构造函数
基本思想是,在子类型的构造函数内部调用超类型的构造函数 ~~~ Function subType(){ superType.call(this); }
Var ins = new subType();
使用这种方式解决了原型方式的共享引用类型属性的问题,而且也可以向构造函数中传递参数,但是又引入了新的问题:方法都在构造函数中定义,函数复用无从谈起。
3. 组合继承
结合以上两种方式,背后思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
Function SuperType(name){ This.name = name; This.colors = [“red”, “blue”]; } SuperType.prototype.sayName = function(){ alert(this.name); };
Function SubType(name, age){ //继承属性 SuperType.call(this, name); This.age = age; }
//继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); };
4. 原型式继承
最初提出时的想法是,借助原型可以基于已有的对象创建新对象
Function object(o){ Function F(){} F.prototype = o; Return new F(); }
感觉和第一种很像,因为基于原型,所以也会出现相似的问题
ECMAScript 5 通过引入Object.create()方法规范了原型式继承,这个方法接受两个参数,一个用作新对象原型的对象,(可选)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法行为相同。如果想覆盖原型中的属性,可以通过指定第二个参数实现,但是依然存在引用类型值属性始终都会共享。可以将这种方式看做第一种原型链方式的简化形式,本质相同。
5. 寄生式继承
这种方式的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后再返回。
Function createAnother(original){ Var clone = object(original); Clone.sayHi = function(){ alert(“hi”); }; Return clone; }
由于不能做到函数复用而降低效率。
6. 寄生组合式继承
前面的组合式继承是最常用的方式,但是它存在的问题是,会调用两次超类型的构造函数,为了改进这个,提出了寄生组合式继承。
基本思路是,不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。基本模式如下
Function inheritPrototype(subType, superType){
Var prototype = object(superType.prototype);
Prototype.constructor = subType;
subType.prototype = prototype;
}
~~~
其实,可以将组合式继承看作是组合继承的封装,它只是将两次调用超类型构造函数的过程借用原型式继承进行了封装,
开放给外界的就不会看到两次调用了,本质上还是组合式继承。
只要是位于构造函数内部的都是定制化的,只要是位于原型里面的都是共同分享的。
只单单使用原型链思想的为 第一种原型链方式和第四种的原型式继承,(1,2,4相同)
注意,原型式继承和原型链继承本质是相同的,区别只是应用的场景不同,
原型链继承的场景是,知道超类型和子类型的构造函数;
原型式继承的场景是,知道超类型的一个实例对象;
只单单使用构造函数的为 第二种方式;
结合使用原型和构造函数的为第三种和第六种,这两种的本质都是相同的,后一种是前一种进一步的封装。