在讲原型关系以前给咱们来看一张图片:java
由图咱们可知几个关系:segmentfault
若是试图引用对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,而后才在该对象的原型(instance.prototype)里去找这个属性.若是还找不到则往原型的原型上找,这样一个层层查找造成的一个链式的关系被称为原型链。 如图: 数组
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//继承 Father
Son.prototype = new Father();//Son.prototype被重写,致使Son.prototype.constructor也一同被重写
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
console.log(instance.getFatherValue());//true
复制代码
可见son实例对象找不到getFatherValue方法,只能前去Father原型那里去找,返回值为true。 若是,对子类son进行改造:bash
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
this.getFatherValue = function(){
return this.sonProperty;
}
}
//继承 Father
Son.prototype = new Father();//Son.prototype被重写,致使Son.prototype.constructor也一同被重写
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
console.log(instance.getFatherValue());//false
复制代码
你会发现当子类里出现相同的方法时,则执行子类中的方法,也就验证了以前的实例对象查找引用属性的过程。函数
使用原型链后, 咱们怎么去判断原型和实例的这种继承关系呢? 方法通常有两种.post
第一种是使用 instanceof 操做符, 只要用这个操做符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回true. 如下几行代码就说明了这点.测试
console.log(instance instanceof Object);//true
console.log(instance instanceof Father);//true
console.log(instance instanceof Son);//true
复制代码
因为原型链的关系, 咱们能够说instance 是 Object, Father 或 Son中任何一个类型的实例. 所以, 这三个构造函数的结果都返回了true.优化
第二种是使用 isPrototypeOf() 方法, 一样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true, 以下所示.ui
console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(Father.prototype.isPrototypeOf(instance));//true
console.log(Son.prototype.isPrototypeOf(instance));//true
复制代码
原型链并不是十分完美, 它包含以下两个问题:this
为此,下面将有一些尝试以弥补原型链的不足.
为解决原型链中上述两个问题, 咱们开始使用一种叫作借用构造函数(constructor stealing)的技术(也叫经典继承).
基本思路:就是在子类的构造函数里调用父类的构造函数。
function Father(){
this.colors = ["red","blue","green"];
function hello() {
console.log('hello world')
}
}
function Son(){
Father.call(this);//继承了Father,且向父类型传递参数
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
复制代码
考虑此,借用构造函数的技术也不多单独使用.
该方法最初由道格拉斯·克罗克福德于2006年在一篇题为 《Prototypal Inheritance in JavaScript》(JavaScript中的原型式继承) 的文章中提出. 他的想法是借助原型能够基于已有的对象建立新对象, 同时还没必要所以建立自定义类型. 大意以下:
基本思路:在create()函数内部, 先建立一个临时性的构造函数, 而后将传入的对象做为这个构造函数的原型,最后返回了这个临时类型的一个新实例.
function create(o){
function Fn() {}
Fn.prototype = o;
return new Fn();
}
复制代码
实质上就是对传入的实例o进行了一次浅拷贝。
function Father(){
this.colors = ["red","blue","green"];
}
let fa = new Father()
var instance1 =create(fa);
instance1.colors.push("black");
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
var instance2 = create(fa);
instance2.colors.push("white");
console.log(instance2.colors); //[ 'red', 'blue', 'green', 'black', 'white' ]
复制代码
在此例中:instance1与instance的原型是同一个对象,当instance1操做原型的引用类型数值,也会影响到instance2。此时数据是共享的。 再看下面这个例子:
function Father(){
this.colors = ["red","blue","green"];
}
var instance1 = create(new Father());
instance1.colors.push("black");
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
var instance2 = create(new Father());
instance2.colors.push("white");
console.log(instance2.colors); // [ 'red', 'blue', 'green', 'white' ]
复制代码
此时因为原型实例不是同一个,数据不在共享。
在 ECMAScript5 中,经过新增 object.create() 方法规范化了上面的原型式继承. object.create() 接收两个参数:
关键点:原型式继承中, 包含引用类型值的属性始终都会共享相应的值, 就像使用原型模式同样.
组合继承, 有时候也叫作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
基本思路:使用原型链实现对原型属性和方法的继承,经过借用构造函数来实现对实例属性的继承.
以下例:
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//继承实例属性,第一次调用Father()
this.age = age;
}
Son.prototype = new Father();//继承父类方法,第二次调用Father()
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
复制代码
在这个例子中,类Son经过构造函数继承能够向父类Father传参,同时可以保证明例数据不被共享。同时经过原型继承能够复用父类的方法,两继承组合起来,各取所需。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,成为 JavaScript 中最经常使用的继承模式. 并且, instanceof 和 isPrototypeOf( )也能用于识别基于组合继承建立的对象.
此处调用了两次父类的构造函数,后面的寄生式组合继承将会对这个问题进行优化。
寄生式继承是与原型式继承紧密相关的一种思路。 基本思路:寄生式继承的思路与(寄生)构造函数和工厂模式相似, 即建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后再像真的是它作了全部工做同样返回对象. 以下.
function createAnother(original){
var clone = create(original);//经过调用create函数建立一个新对象
clone.sayHi = function(){//以某种方式来加强这个对象
alert("hi");
};
return clone;//返回这个对象
}
复制代码
直白点,所谓寄生式继承也就是在其余继承方式(构造继承、原型继承等)上增长新的功能,返回新的对象。
前面讲过,组合继承是 JavaScript 最经常使用的继承模式; 不过, 它也有本身的不足. 组合继承最大的问题就是不管什么状况下,都会调用两次父类构造函数: 一次是在建立子类型原型的时候, 另外一次是在子类型构造函数内部. 寄生组合式继承就是为了下降调用父类构造函数的开销而出现的 .以下例:
function extend(subClass,superClass){
var prototype = create(superClass.prototype);//建立对象
prototype.constructor = subClass;//加强对象
subClass.prototype = prototype;//指定对象
}
复制代码
下面咱们来看下extend的另外一种更为有效的扩展.
// 把上面的 create 拆开,其实差很少。
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}
复制代码
console.log(instance1.hasOwnProperty('age'));//true
复制代码
console.log(Father.prototype.isPrototypeOf(instance1));//true
复制代码
instanceof 运算符是用来在运行时指出对象是不是构造器的一个实例, 例如漏写了new运算符去调用某个构造器, 此时构造器内部能够经过 instanceof 来判断.(java中功能相似)
function f(){
if(this instanceof arguments.callee)
console.log('此处做为构造函数被调用');
else
console.log('此处做为普通函数被调用');
}
f();//此处做为普通函数被调用
new f();//此处做为构造函数被调用
复制代码
new实质上作了三件事;
var obj = {};
obj.__proto__ = F.prototype;
F.call(obj); //执行
复制代码
第一行,咱们建立了一个空对象obj; 第二行,咱们将这个空对象的__proto__成员指向了F函数对象prototype成员对象; 第三行,咱们将F函数对象的this指针替换成obj,而后再调用F函数.
咱们能够这么理解: 以 new 操做符调用构造函数的时候,函数内部实际上发生如下变化:
在继承关系里,内部属性数值变不变,数据共不共享前面也有所介绍,可是不够具体。这块时常使人迷惑,决定单独拿出来说讲:
首先在继承关系里,原型继承与构造函数继承能够分红两个比较重要的继承关系,其余的继承都是在这基础上演变组合出来的,因此搞懂这两个继承关系中的数据变化,就差很少了。
在讲区别以前咱们先两个例子:
function kk() {
this.a = 3;
this.k = {l: 5};
}
function j() {
kk.call(this)
}
let m = new j();
m.a = 9;
m.k.l = 9;
let n = new j();
console.log(n.a, n.k.l);
复制代码
打印结果:
function kk() {
this.a = 3;
this.k = {l: 5};
}
function j() {
}
j.prototype = new kk();
let m = new j();
m.a = 9;
m.k.l = 9;
let n = new j();
console.log(n.a, n.k.l);
复制代码
打印结果:
若是你的眼睛足够雪亮,会一眼看出上例是构造函数继承,下例是原型继承,它两的区别以前已经说过,构造函数继承数据不会共享,而原型继承会共享。因而你会说为何a怎么不变,你又在忽悠人,哼!哈哈哈,抱歉,有没有看见a是基本数据类型,k是引用类型(引用类型包括:对象、数组、函数。)啊,基本数据类型是指针的指向区别,引用类型是地址的指向区别。不了解这块能够看看这篇文章:segmentfault.com/a/119000000…
使用权威指南6.2.2继承那块的一句话“若是容许属性赋值操做,它也老是在原始对象上创造属性或者对已有属性赋值,而不会修改原型链,在JavaScript里,只有查询属性才能感觉到继承的存在,而设置属性则与继承无关”。
如何理解这句话?我想是指继承关系中属性在自己内部找不到的时候才会去原型里找,只是借用属性,可是并不会修改原型自己的属性值,这也就解释了基本数据类型始终不变的缘由。而原型继承中因为使用的同一原型对象,里面的引用类型使用同一个地址,致使应用类型的数值是能够变化的。
总结两点:
参考地址: juejin.im/post/58f94c…