说说JavaScript的prototype(内容源自《你所不知道的JavaScript》)

首先给大家看一段JavaScript里面的经典“继承”代码——但其实并不是真正的继承,只是利用了原型链。

function f1(){
    f2.call(this);
    this.a = 2;
}
//f1“继承”f2
function f2(){
    this.b = 3;
}

//核心代码
var fpro = Object.create(f2.prototype);
fpro.constructor = f1;
f1.prototype = fpro;

//此时f1的实例可以访问f2的变量。
var f1_1 = new f1();
console.log(f1_1.b);

如果上面这个大家看懂了,那么对于prototype可以说就有了正确的理解。

下面,我们从头开始说:

几乎所有的对象在创建时都会同时被创建一个 [[Prototype]]内置属性。
但是,[[Prototype]] 引用有什么用呢?
当你试图访问这个对象的属性的时候,第第一步是检查对象本身是否有这个属性,如果有的话就使用它。但是如果这个属性并不在对象本身呢,那就要使用对象的 [[Prototype]] 链了!

也就是说:如果无法在对象本身找到需要的属性,就会继续访问对象的 [[Prototype]] 链:

var anotherObject = { 
    a:2
};
// 创建一个关联到 anotherObject 的对象
var myObject = Object.create( anotherObject ); 
myObject.a; // 2

// Object.create() 方法创建一个拥有指定原型(和若干个指定属性)的对象。
// 详细解释可参阅:
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

因为[[Prototype]] 链的存在,上面的myObject对象才可以访问到anotherObject的a属性。但是如果在 anotherObject 中也找不到 a属性 并且 [[Prototype]] 链不为空的话,就会继续查找下去。

这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链。如果是后者并且没有找到匹配属性的话, 那么返回值就是 undefined。

但是到哪里是 [[Prototype]] 的“尽头”呢?
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。由于所有的“普通” (内置,不是特定主机的扩展)对象都“源于”(或者说把 [[Prototype]] 链的顶端设置为)这个 Object.prototype 对象,所以它包含 JavaScript 中许多通用的功能,比如说.toString() .valueOf() .hasOwnProperty(..) .isPrototypeOf(..)等等。

属性设置和屏蔽问题

由于又了prototype,那么,属性的设置就不仅仅是添加一个新属性或者修改已有的属性值。还会涉及到这个属性的原型链。现在我们完整地讲解一下这个过程:

myObject.foo = "bar";

1、如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。

2、如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,类似 [[Get]] 操作。如果原型链上找不到 foo,foo 就会被直接添加到 myObject 上。

3、然而,如果 foo 存在于原型链上层,赋值语句 myObject.foo = “bar” 的行为就会有些不同,这就会涉及到属性屏蔽的问题:myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为 myObject.foo 总是会选择原型链中最底层的 foo 属性。
也就是说,在查找的过程中,会以沿着原型链查找过程中最先找到的那个属性为准!

下面来讨论属性不直接存在于 myObject 中而是存在于原型链上层时myObject.foo = “bar”会出现的三种情况:

1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性并且没有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新属性,它是屏蔽属性。

2. 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。

3. 如果在[[Prototype]]链上层存在foo并且它是一个setter(赋值操作),那就一定会调用这个 setter。foo不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这个 setter。换句话说,就是会直接调用同名函数,而不做任何赋值操作。

如果你希望在第二种和第三种情况下也屏蔽 foo,那就不能使用 = 操作符来赋值,而是使用 Object.defineProperty(..)方法来向 myObject 添加 foo。

上面介绍的是对象的[[Prototype]],接下来是函数的prototype属性。

函数的一种特殊特性:所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象:

function Foo() { // ...
}
Foo.prototype; // { }

这个对象通常被称为 Foo 的原型,因为我们通过名为 Foo.prototype 的属性引用来访问它。

首先我们来看下面这段代码:

function Foo() { 
    // ...
}
var a = new Foo();
Object.getPrototypeOf( a ) === Foo.prototype; // true

调用 new Foo() 时会创建 a对象,其中的一步就是给 a 一个内部的 [[Prototype]] 链接,关联到 Foo.prototype 指向的那个对象。

换一句话来说:
new Foo()会生成一个新对象,这个新对象的内部链接[[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.constructor 了   
// 如果你需要这个属性的话可能需要手动修复一下它
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);
Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象。

声明function Bar() { .. }时,和其他函数一样,Bar会有一个prototype关联到默认的对象,但是这个对象并不是我们想要的 Foo.prototype。因此我们创建了一个新对象并把它关联到我们希望的对象上,直接把原始的关联对象抛弃掉。

注意,下面这两种方式是常见的错误做法,实际上它们都存在一些问题:

// 和你想要的机制不一样! 
Bar.prototype = Foo.prototype;

// 基本上满足你的需求,但是可能会产生一些副作用 :
Bar.prototype = new Foo();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值