javascript—原型

JavaScript的对象有一个特殊的[[Prototype]]内置属性,它是对其他对象的引用。几乎所有对象在创建时Prototype属性都会被赋予一个非空的值。有些对象的[[Prototype]]属性可能为空,但是很少见。
Prototype属性有什么用呢?比如当试图访问对应的属性时,会触发[[Get]]操作获取对应的属性值,例如myObject.a。对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有就使用它。如果没有,会使用[[Prototype]]属性引用的对象继续查找,直到找到属性为a的值并返回该值,或者遍历[[Prototype]]链的最后并返回undefined。

var anotherObject = {
    a: 2
}
// 创建一个关联到anotherObject的对象,即创建对象的[[Prototype]]属性引用anotherObject对象
var myObject = Object.create(anotherObject);
myObject.a; // 2

使用for…in遍历对象时,任何可以通过原型链访问到的属性都会被枚举。使用in操作符检查对象属性是否存在时,同样会查找对象的整个原型链。

var anotherObject = {
    a: 2
};
var myObject = Object.create(anotherObject);
for (var k in myObject) {
    console.log("found: " + k);
}
// found: a
("a" in myObject);  // true

所有普通对象的[[Prototype]]链最终都指向Object.prototype,因此这些对象包含了JavaScript中许多通用功能,例如:toString()、valueOf()、hasOwnProperty()、isPrototypeOf()。

1、属性设置和屏蔽

思考一下代码:

myObject.foo = "bar";

如果myObject对象包含foo属性。这条赋值语句只会修改已有的属性值。如果myObject对象不包含foo属性,就会遍历[[Prototype]]链查找foo属性,如果找不到就会在myObject上直接添加foo属性。如果找到,分三种情况处理:
1. 如果[[Prototype]]链上的foo属性且没有被标记为只读(writable: false),则会直接在myObject对象中添加foo属性,这个属性为屏蔽属性,它会屏蔽[[Prototype]]链上的其他同名属性。因为属性的查找总是从[[Prototype]]链的底层向高层查找,找到即返回,所以底层属性会屏蔽高层同名属性。
2. 如果[[Prototype]]链上的foo属性且被标记为只读(writable: false),则赋值操作会被忽略,如果在严格模式下会抛错。
3. 如果[[Prototype]]链上存在的foo且它是一个setter,那就一定会调用这个setter。foo不会被添加到myObject对象上,也不会重新定义foo这个setter。

2、面向对象编程

在JavaScript中函数有一种特设特性:所有的函数默认都会拥有一个prototype的公有且不可枚举的属性,它会指向一个对象,这个对象通常被称为函数的原型。在调用new关键字创建对象时,创建的对象的[[Prototype]]链接会关联到该函数的prototype属性引用的对象。
在JavaScript中可以通过[[Prototype]]属性实现面向对象编程,因为JavaScript中只有对象没有类。例如:

function Foo(name) {
    this.name = name;
}

Foo.prototype.myName = function() {
    return this.name;
}

var a = new Foo("a");
var b = new Foo("b");

a.myName(); // "a"
b.myName(); // "b"

Foo只是一个普通函数,但是通过使用new关键字调用就变成了构造函数,也就是说构造了一个对象。所有Foo被认为是构造函数,实际上Foo函数和其他函数没有任何区别,也不是构造函数。但是当你在普通函数调用前加new关键字后,该函数调用就变成一个”构造函数调用”,因为它创建了一个对象。其实,new会劫持所有普通函数并用构造对象的形式来调用它。例如:

// 普通函数
function NothingSpecial() {
    console.log("Don't mind me!");
}
// 使用new关键字调用,构造了一个对象
var a = new NothingSpecial();   // "Don't mind me!"
a; // {}

Foo.prototype.muName = …操作给Foo.prototype对象添加一个方法。在对象a和b上调用myName()方法可以正常工作。其实在创建a和b对象时,new操作会将创建的对象的[[Prototype]]属性关联到Foo.prototype对象。所以根据对象的属性查找规则,如果对象的直接属性没有要查找的属性时,会通过[[Prototype]]链继续查找,于是在Foo.prototype上找到。
使用原型实现面向对象编程的继承,例如:

function Foo(name) {
    this.name = name;
}

Foo.prototype.myName = function () {
    return this.name
};

function Bar(name, label) {
    Foo.call(this, name);
    this.label = label;
}
// 创建新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype)

Bar.prototype.myLabel = function () {
    return this.label;
}

var a = new Bar("a", "obj a");
a.myName(); // a
a.myLabel();    // "obj a"

这段代码的核心语句是Bar.prototype = Object.create(Foo.prototype),创建新对象并把新对象内部的[[Prototype]]关联到Foo.prototype,把新对象赋值给Bar.prototype。于是形成了Bar–>Foo的类似面向对象编程中的继承关系,其中Bar是子类,Foo是父类。只是行为类似继承关系,底层原理是完全不同的。
但是使用Object.create()方法需要创建新对象然后把旧对象抛弃,不能直接修改已有的默认对象。在ES6添加了赋值函数Object.setPrototypeOf()方法,用标准且可靠的方法来修改关联。

Object.setPrototypeOf(Bar.prototype, Foo.prototype);

使用Object.create(…)创建一个新对象并把它关联到指定的对象,可以充分发挥[[Prototype]]机制的威力并避免用new的构造函数调用会生成prototype和constructor引用。例如:

var foo = {
    something: function() {
        console.log("tell something...")
    }
};
var bar = Object.create(foo);
bar.something();    // tell something...

Object.create(null)会创建一个拥有空[[Prototype]]链接的对象,这些特殊的空[[Prototype]]对象通常被称为“字典”,不会受到原型链的干扰,因此很适合用来存储数据。

猜你喜欢

转载自blog.csdn.net/Shie_3/article/details/81429146
今日推荐