一、原型链 是什么?
JavaScript 的每个实例对象都有一个指向上一层对象的私有属性(称之为
__proto__
) ,上一层对象又指向再上一层对象,就这样层层向上直至
__proto__
属性为 null ,最后这个对象就是
Object
。
这种通过
__proto__
属性来链接对象的方法,称之为
原型链
。
var o = {a: 1};
// 原型链如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
function f() {
this.a = 1;
this.b = 2;
}
let o = new f();
f.prototype.b = 3;
f.prototype.c = 4;
// 原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
二、怎么使用?
1、Object.getPrototypeOf() 和 Object.setPrototypeOf()
ECMAScript 6 开始,可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问
__proto__
属性。
function f() {
this.a = 1;
this.b = 2;
}
let o = new f();
// 1、为 f 的上层对象新增属性(推荐此种方式新增属性)
f.prototype.b = 3;
f.prototype.c = 4;
// 2、查看上层对象属性
console.log(Object.getPrototypeOf(o));
// 3、此种方式设置上层对象的属性,会覆盖掉所有旧属性,慎用
console.log(Object.setPrototypeOf(o, {e:5}));
// 4、查看新的上层对象属性
console.log(Object.getPrototypeOf(o));
2、prototype 与 Object.getPrototypeOf
两者功能一致,都是用来访问原型属性,区别是:
- prototype 用于类,而 Object.getPrototypeOf() 用于实例;
- 共享:所有实例都可以访问,新增的 prototype 属性。
function A(){
this.a = "a";
}
let aIns = new A();
// 1、一个用于类型、一个用于实例
console.log(A.prototype);
console.log(Object.getPrototypeOf(aIns));
let bIns = new A();
// 2、共享:所有实例都可以访问 新增的原型属性
A.prototype.b = "b";
console.log(Object.getPrototypeOf(aIns).b);
console.log(Object.getPrototypeOf(bIns).b);
var o = new Foo();
// 上面代码是让 JavaScript 执行了下面代码
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);
3、hasOwnProperty
当访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻原型链上的所有对象,最后判断属性是否存在。hasOwnProperty 是 JavaScript 中唯一一个不会遍历原型链的方法。
const object1 = {};
object1.property1 = 42;
console.log(object1.hasOwnProperty('property1'));
// expected output: true
console.log(object1.hasOwnProperty('toString'));
// expected output: false
4、undefined
没有赋值的属性是 undefined,所以无法通过 undefined 来判断属性是否存在。
三、继承
继承也是基于原型链的特性。ECMAScript6 引入 class 关键字来实现 类对象,但类对象也是基于原型链。
1、Object.create
用 Object.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
四、其他
1、性能
试图访问不存在的属性时会遍历整个原型链,在原型链上查找属性比较耗时,对性能有副作用。
2、错误实践
扩展内置原型会破坏封装,这不是好的解决方案,使用扩展内置原型的唯一理由是支持 JavaScript 引擎的新特性,如
Array.forEach
。
原生原型
不应该
被扩展,除非它是为了与新的 JavaScript 特性兼容。
五、参考文档