1.对象的 [[Prototype]]属性
每个对象都有[[Prototype]]属性,用于指向对象的原型对象。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
遵循ECMAScript标准,someObject.[[Prototype]] 符号是用于指向 someObject的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性__proto__ 。
下面有个例子,手动修改函数的[[Prototype]]属性来构造一个原型链。
// http://jsbin.com/wovunob/4/edit?js,console
const a = {x: 1};
const b = {y: 2};
const c = {z: 3};
a.__proto__ = b
b.__proto__ = c
console.log(a.x) //1
console.log(a.y) //2
console.log(a.z) //3
因此 [[Prototype]]属性是javascript原型链的基础。
这引出一个问题,可是到底对象的原型又是什么呢?下面会讲到
关于代码中直接操作__proto__属性: 其实官方是建议使用Object.setPrototypeOf 和 Object.getPrototypeOf ,我这里偷个懒,工作中还是要认真做。
2. 函数的prototype属性
只有函数才有prototype属性。
当你创建函数时,JS会为这个函数自动添加prototype属性,指向一个对象。这个对象含有constructor属性,你也可以在里面添加一些方法来实现对象之间的方法函数的共享。

而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的对象实例。我们前面知道对象都是有[[Prototype]]属性的,该对象的属性指向自己的原型对象,我们发现原型对象正是构造函数的prototype指向的对象。正因为如此,新生成的对象才可以继承构造函数的所有属性和方法。

从上面的例子中,我们对对象的原型有个直观的了解。
3.原型对象中的constructor属性
constructor属性指向的是对象的构造函数。

4.不同生成对象的方法和原型链
4.1 语法结构创建的对象
var o = {a: 1};
// o 这个对象继承了Object.prototype上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函数都继承于Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
4.2 构造器创建的对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g是生成的对象,他的自身属性有'vertices'和'edges'.
// 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.
4.3 Object.create 创建的对象
ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数
这实际就是进行继承了对象方法
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype
Object.create的内部实现:
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
function F() {}
F.prototype = proto;
return new F();
};
}
5.类的继承
继承是一种在新对象上复用现有对象的属性的形式,这有助于避免重复代码和重复数据。 js中的继承其实就是将构造函数的prototype指向一个要继承的类的对象。
function Person(){}
Person.prototype.dance = function(){}
function Ninja(){}
Ninja.prototype = new Person()
const n = new Ninja();
console.log(n instanceof Ninja) //true
console.log(n instanceof Person) //true
console.log('dance' in n) //true
console.log(n.constructor === Person) //true
此时Ninja其实已经继承了Person,但是要还要修改原型对象的constructor属性 这里使用Object.defineProperty来改变属性。 之所以不直接对Ninja.prototype.constructor进行赋值,是因为这么做会导致constructor属性被遍历到。
function Person(){}
Person.prototype.dance = function(){}
function Ninja(){}
Ninja.prototype = new Person()
Object.defineProperty(Ninja.prototype , 'constructor' ,{
enumerable:false,
writable:true,
value:Ninja
})
const n = new Ninja();
console.log(n instanceof Ninja) //true
console.log(n instanceof Person) //true
console.log('dance' in n) //true
console.log(n.constructor === Ninja) //true
console.log(Object.keys(Ninja.prototype)) // []
6.ES6中的class
ES6中引入了新的关键字class,它提供了一种更为优雅的创建对象和实现继承的方式,底层仍然是原型链。
1.使用class创建类
使用方法很简单
class Person{
constructor(name){
this.name = name
}
dance(){
return true
}
}
const p = new Person('lilei');
console.log(p.name) //"lilei"
console.log(p.dance()) //true
其实class创建的类的类型还是一个函数,仍然具有prototype属性,对象的[[prototype]]仍然指向这个原型对象,有区别的是constructor变了class,这个暂时无法理解。

2.使用class进行继承
class Ninja extends Person 就可以了
不用再考虑原型或者覆盖属性的副作用了。
http://jsbin.com/wovunob/11/edit?js,console
class Person{
constructor(name){
this.name = name
}
dance(){
return true
}
}
class Ninja extends Person{
constructor(name,weapon){
super(name)
this.weapon = weapon
}
wieldWeapon(){
return true
}
}
const p = new Person('lilei');
console.log(p.name) //"lilei"
console.log(p.dance()) //true
const n = new Ninja('Yoshi','sword')
console.log(n instanceof Person) //true
console.log(n instanceof Ninja) //true
console.log(n.name) //"Yoshi"
console.log(n.dance()) //true
console.log(n.wieldWeapon()) //true
console.log(n.constructor === Ninja) //true
console.log(n.__proto__ === Ninja.prototype) //true
console.log(n.__proto__.__proto__ === Person.prototype) //true
7.其他
7.1 instanceof
7.2 hasOwnProperty
7.3 改变原型对象的副作用
参考文章
developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
github.com/creeperyang/blog/issues/9
javascript忍者秘籍