1,原型链
JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object的实例。
1 | // 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的: |
2,属性查找逻辑
属性是自上向下依次查找属性,直到查找到结果停止。例子如下:
代码
1
2
3
4
5
6
7
8
9
10
11function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomething.prototype );
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);在google浏览器其中获得结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}执行结果
1
2
3
4
5
6doSomeInstancing.prop: some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar分析结果
当你访问doSomeInstancing 中的一个属性,浏览器首先会查看doSomeInstancing 中是否存在这个属性。
如果 doSomeInstancing 不包含属性信息, 那么浏览器会在 doSomeInstancing 的 proto 中进行查找(同 doSomething.prototype). 如属性在 doSomeInstancing 的 proto 中查找到,则使用 doSomeInstancing 中 proto 的属性。
否则,如果 doSomeInstancing 中 proto 不具有该属性,则检查doSomeInstancing 的 proto 的 proto 是否具有该属性。默认情况下,任何函数的原型属性 proto 都是 window.Object.prototype. 因此, 通过doSomeInstancing 的 proto 的 proto ( 同 doSomething.prototype 的 proto (同 Object.prototype)) 来查找要搜索的属性。
如果属性不存在 doSomeInstancing 的 proto 的 proto 中, 那么就会在doSomeInstancing 的 proto 的 proto 的 proto 中查找。然而, 这里存在个问题:doSomeInstancing 的 proto 的 proto 的 proto 其实不存在。因此,只有这样,在 proto 的整个原型链被查看之后,这里没有更多的 proto , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。
3,性能
分析
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从
Object.prototype
继承的hasOwnProperty
方法。例子
1
2
3
4
5
6
7
8
9
10
11console.log(g.hasOwnProperty('vertices'));
// true
console.log(g.hasOwnProperty('nope'));
// false
console.log(g.hasOwnProperty('addVertex'));
// false
console.log(g.__proto__.hasOwnProperty('addVertex'));
// true备注
1,hasOwnProperty 是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys())
2,检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。
4,创建对象
语法结构创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var 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构造器创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14function 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。Object.create创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13var 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.prototypeClass关键字创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23;
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
5,扩展原型链方法
下面列举四种用于拓展原型链的方法,以及他们的优势和缺陷。下列四个例子都创建了完全相同的
inst
对象(所以在控制台上的输出也是一致的),为了举例,唯一的区别是他们的创建方法不同。New-initialization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
var proto = new foo;
proto.bar_prop = "bar val";
function bar(){}
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop); //foo val
console.log(inst.bar_prop); //bar val
//此时inst 是一个objectObject.create
1
2
3
4
5
6
7
8
9
10
11
12
13function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
var proto = Object.create(foo.prototype);
proto.bar_prop = "bar val";
function bar(){}
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);Object.setPrototypeOf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
var proto = {
bar_prop: "bar val"
};
Object.setPrototypeOf(proto, foo.prototype);
function bar(){}
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
var proto;
proto=Object.setPrototypeOf(
{ bar_prop: "bar val" },
foo.prototype
);
function bar(){}
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)prop
1
2
3
4
5
6
7
8
9
10
11
12
13function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = {
bar_prop: "bar val",
__proto__: foo.prototype
};
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);1
2
3
4
5
6
7
8
9
10
11var inst = {
__proto__: {
bar_prop: "bar val",
__proto__: {
foo_prop: "foo val",
__proto__: Object.prototype
}
}
};
console.log(inst.foo_prop);
console.log(inst.bar_prop)
优缺点分析
名称 优势 缺陷 New-initialization 支持目前以及所有可想象到的浏览器(IE5.5都可以使用)。 这种方法非常快,非常符合标准,并且充分利用JIT优化。 为使用此方法,必须对相关函数初始化。 在初始化过程中,构造函数可以存储每个对象必须生成的唯一信息。但是,这种唯一信息只生成一次,可能会带来潜在的问题。此外,构造函数的初始化,可能会将不需要的方法放在对象上。然而,如果你只在自己的代码中使用,你也清楚(或有通过注释等写明)各段代码在做什么,这些在大体上都不是问题(事实上,通常是有益处的)。 Object.create 支持当前所有非微软版本或者 IE9 以上版本的浏览器。允许一次性地直接设置 __proto__
属性,以便浏览器能更好地优化对象。同时允许通过Object.create(null)
来创建一个没有原型的对象。不支持 IE8 以下的版本。然而,随着微软不再对系统中运行的旧版本浏览器提供支持,这将不是在大多数应用中的主要问题。 另外,这个慢对象初始化在使用第二个参数的时候有可能成为一个性能黑洞,因为每个对象的描述符属性都有自己的描述对象。当以对象的格式处理成百上千的对象描述的时候,可能会造成严重的性能问题。 Object.setPrototypeOf 支持所有现代浏览器和微软IE9+浏览器。允许动态操作对象的原型,甚至能强制给通过 Object.create(null)
创建出来的没有原型的对象添加一个原型。这个方式表现并不好,应该被弃用。如果你在生产环境中使用这个方法,那么快速运行 Javascript 就是不可能的,因为许多浏览器优化了原型,尝试在调用实例之前猜测方法在内存中的位置,但是动态设置原型干扰了所有的优化,甚至可能使浏览器为了运行成功,使用完全未经优化的代码进行重编译。 不支持 IE8 及以下的浏览器版本。 proto 支持所有现代非微软版本以及 IE11 以上版本的浏览器。将 __proto__
设置为非对象的值会静默失败,并不会抛出错误。应该完全将其抛弃因为这个行为完全不具备性能可言。 如果你在生产环境中使用这个方法,那么快速运行 Javascript 就是不可能的,因为许多浏览器优化了原型,尝试在调用实例之前猜测方法在内存中的位置,但是动态设置原型干扰了所有的优化,甚至可能使浏览器为了运行成功,使用完全未经优化的代码进行重编译。不支持 IE10 及以下的浏览器版本。
备注,参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain