在JavaScript中,全部的东西都是对象,可是JavaScript中的面向对象并非面向类,而是面向原型的,这是与C++、Java等面向对象语言的区别,比较容易混淆,所以把我本身学习的过程记录下来。编程
首先说,原型链有什么用?在Java中,继承都是基于类的,在JavaScript中继承都是基于原型链的。也就是说在JavaScript中,原型链是实现继承的基础,想要掌握好JavaScript中的面向对象编程,必须对原型链有必定的了解。浏览器
要理解原型链,必须先了解两个对象,一个是 prototype ,另外一个是 __proto__ 。当前只须要记住名字,下面会仔细说明。函数
首先是 prototype : prototype ,或者叫原型对象,是函数特有的一个属性,其类型是 Object ,所以也经常被称做函数的原型对象。虽然每一个函数都拥有本身的原型对象,但只有用做构造函数时,这个属性才会发挥做用,关于构造函数的知识这里不说。原型对象其实很简单,他就是一个普通的 Object ,当其做为构造函数时默认有一个 constructor 。学习
1 // 举个简单的例子 2 function fn() { } 3 4 console.log(fn.prototype); // {}
咱们能够给原型对象添加一些方法或属性,就能够被其子类继承:this
1 // 后面会讲怎么继承 2 function fn() { } 3 4 // 添加一个方法 5 fn.prototype.sayHello = function() { 6 alert('hello'); 7 }; 8 9 // 添加一个属性 10 fn.prototype.name = 'my_fn';
上面就完成了对原型对象的介绍,接下来是 __proto__ ,这是一个全部对象都拥有的属性。其实 __proto__ 与原型对象密不可分,由于一个对象的 __proto__ 就是指向其构造函数的原型对象。须要注意的是 __proto__ 并非JavaScript的规范,只是大多数浏览器都实现了,从ECMAScript 6开始,应该用Object.getPrototypeOf()和Object.setPrototypeOf()来访问这个属性。看一个例子:spa
1 function fn() { } 2 3 var f = new fn(); 4 console.log(f.__proto__ === fn.prototype); // true
从上面的代码,应该就能够明白这两者的关系了,若是能理解这一点,接下来就能够开始分析继承的实现原理了。prototype
开头说过,继承是基于原型链实现的,那么什么是原型链呢?首先咱们看几个例子:设计
1 function fn() { } 2 3 // 首先记住,一个对象的__proto__指向它的构造函数的原型对象 4 var f = new fn(); 5 console.log(f instanceof Object); // true,说明此时f是一个Object 6 console.log(f.__proto__ === fn.prototype); // true,没毛病,由于f的构造函数就是fn 7 var obj = fn.prototype; // 咱们看看fn的原型对象是什么类型?确定是对象! 8 console.log(obj.__proto__ === Object.prototype); // true,那对象就是Object,它的构造函数就是Object 9 obj = Object.prototype; // 那Object的原型对象应该也是个对象吧 10 console.log(obj.__proto__ === null); // true,为何是null?这是JavaScript设计的,由于若是不是null,就会无限循环
以上的例子说明了f的构造函数的原型,f的构造函数的原型的原型,f的构造函数的原型的原型的原型,用图形表示就是:code
f.__proto__ ---> f.__proto__.__proto__ ---> f.__proto__.__proto__.__proto__
那么上面这条“链”就是咱们所说的原型链了!这个过程理解了能够继续往下。对象
那么原型链是如何实现继承的?在JavaScript中,你对一个对象调用一个方法或者获取一个属性,它就会自动的在原型链上面寻找,一直到找到或者原型对象为null。
1 // 再举个例子 2 function fn() {} 3 4 var f = new fn(); 5 6 // 此时fn并无方法toString 7 console.log(f.toString()); // 输出[object Object] 8 // 为何? 9 // 按照刚刚分析的原型链,它会如今fn.prototype中寻找 10 console.log(fn.prototype); // fn {},没有 11 // 再在fn.prototype.__proto__(Object.prototype)中寻找 12 console.log(fn.prototype.__proto__);
上面这个例子说明,f的 toString 方法实际上是从 Object.prototype 继承而来的。
若是上面这些都能明白,那咱们就能够本身实现继承了。
1 // 回到前面的例子 2 function SuperClass() { 3 this.time = new Date().toLocaleString(); 4 } 5 6 // // 添加一个方法 7 SuperClass.prototype.sayHello = function() { 8 console.log('hello'); 9 }; 10 11 // // 添加一个属性 12 SuperClass.prototype.name = 'super'; 13 14 // 定义一个子类继承SuperClass 15 function SubClass() { 16 // 在子类的构造函数中调用父类的构造函数 17 SuperClass.call(this); 18 } 19 20 SubClass.prototype = Object.create(SuperClass.prototype); // 继承父类的属性和方法 21 // SubClass.prototype = SuperClass.prototype; // 不能直接赋值!由于JavaScript中的对象赋值都是浅复制,有反作用,也就是说修改子类的原型也会修改父类 22 // SubClass.prototype = new SuperClass(); // 也不要这样作,由于这样会实例化一个SuperClass,假如SuperClass的构造函数中定义了一个很大的对象,就会形成内存浪费! 23 SubClass.constructor = SubClass; // 上一行代码会把子类的构造函数给覆盖了,这里把它恢复了。注意:constructor会影响instanceof运算符的结果 24 25 SubClass.prototype.subfn = function() { 26 console.log('this is a sub func'); 27 } 28 29 var sc = new SubClass(); 30 console.log(sc.time); // 2018-12-18 22:02:09 31 sc.sayHello(); // hello 32 sc.subfn(); // this is a sub func
上面就是一个简单的继承,要实现多重继承也是相似的:
1 // 在上面的代码修改 2 // 另外一个父类 3 function SuperClassB() {} 4 SuperClassB.prototype.anotherfn = function() { 5 console.log('another super'); 6 } 7 8 // ... 9 10 // 定义一个子类继承SuperClass 11 function SubClass() { 12 // 在子类的构造函数中调用父类的构造函数 13 SuperClass.call(this); 14 SuperClassB.call(this); // 添加 15 } 16 17 var prototype = Object.create(SuperClass.prototype); // 继承父类的属性和方法 18 prototype = Object.assign(prototype, SuperClassB.prototype); // 合并两个父类的原型对象 19 SubClass.prototype = prototype // 继承父类的属性和方法 20 21 // ... 22 23 sc.anotherfn(); // another super