js-原型和原型链

之前一直对与原型和原型链这个东西感觉很不好理解,网上的解析都是一张图,然后箭头指向来指向去的,看的实在是有点头晕,所以今天详细了解了一下这个东西,写一篇博客,以供自己查阅。

1、prototype 与 __proto__

首先,任何对象都存在__proto__属性,虽然这个属性是不标准的,真正标准的是[[protopype]]。

var bar = {goo: undefined};
bar.__proto__
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

函数也是一个特殊对象,所以函数也会存在__proto__,但是,只有函数才会存在 prototype 属性

function foo() {
  this.add = function (x, y) {
    return x + y;
  }
}
foo.__proto__
ƒ () { [native code] }
foo.prototype
{constructor: ƒ}

2、prototype 与 __proto__ 区别

 首先,我们存在一个函数,以此来充当构造函数

function foo() {
  this.add = function (x, y) {
    return x + y;
  }
}

 这个函数存在的 prototype 属性我们可以详细看一下:

  • 一个 constructor 属性:指向当前构造函数 foo()
  • 一个 __proto__ 属性:指向的是Object(这是因为原型链上没有其他的原型了,直接就到了Object上

然后,通过这个构造函数实例一个对象出来看看是什么情况

var f = new foo();

文章开头说过,对象只存在 __proto__ 属性 ,所以直接打印 __proto__ 属性看下

 对比一下就会发现,这个构造函数的 prototype 属性 ,和通过这个构造函数实例化的对象的 __proto__ 是一模一样的

foo.prototype === f.__proto__
true

  也就是可以看作,我在实例化对象上找东西的时候,就相当于在构造函数的 prototype 属性上找东西。

由此,引出了原型链的概念

3、原型链

MDN上是这么定义的 :当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

这段代码拷贝于汤姆大叔的博客,链接为:https://www.cnblogs.com/TomXu/archive/2012/01/05/2305453.html

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;

var test = new Bar() // 创建Bar的一个新实例

下面我将按照我的理解,对这段代码进行分解

1、首先,编写一个函数 Foo 作为构造函数,函数内部存在一个属性值 value = 42

2、将函数 Foo的原型 prototype 设置为 一个对象,对象内部存在一个 名叫 method 的方法函数

3、编写一个 函数Bar 作为 第二个构造函数,以此形成原型链

4、将 函数Bar 的 原型prototype 设置为构造函数 Foo 的实例对象,此时,函数 Foo 内部的 value 属性,函数Foo 的 prototype 属性上设置的 对象,都被 Bar.prototype 继承得到。打印  Bar.prototype得到如下值

       

5、此时,因为,value 属性是直接在函数Foo 内部定义的,所以在实例化的时候,能直接拿到 value 属性,但是 method 方法,是在 函数Foo 的 原型prototype 上的,所以不能直接拿到,这个时候就需要用到 __proto__ 属性了。

6、Bar.prototype 虽然是 函数Bar 的原型属性,但是同时,它也是 构造函数Foo 的实例对象,根据上述第二点概念,构造函数的prototype 属性 === 实例化对象的 __proto__ 。也就是如下情况

Bar.prototype.__proto__ === Foo.prototype
true

7、在 Bar.prototype 上继续新增一个 foo 属性,值为 'Hello World',继续打印 Bar.prototype。可以看到,Bar.prototype 打印之后,直接多了一个 foo 属性

Bar.prototype
{
  value: 42
  foo: "Hello World"
  __proto__: {
    method: ƒ ()
    __proto__: Object
  }
}

8、按照js的原型规则,构造函数原型prototype上的 constructor 属性,应该是指向 构造函数本身的,如果是在 原型prototype 上添加属性的话,是没有问题的。但是 如果是直接给原型 赋值,那么就会导致 该构造函数的原型上失去 constructor 这个属性。所以需要重新赋值。

9、实例化构造函数 Bar,继承 构造函数 Bar 上的属性,以及构造函数Bar 原型prototype 上的属性

所以 实例化的 test对象下的__proto__ === 构造函数Bar的原型prototype ,也就是

test.__proto__ === Bar.prototype
true

10、然后开始了最重要的原型链。

我需要在 test 对象下面找一个 method 函数。但是我们知道,test 对象下是不存在这个函数的,我们代码里面就没有写进去过。

这个时候,js就会开始向上查找了。 test对象没有,那我就去 test.__proto__ 里面去找( 这里就是相当于,在构造函数Bar.prototype 上查找了 )。 

test.__proto__ === Bar.prototype
true

因为test.__proto__也是一个对象,那就是也是存在 __proto__ 属性,test.__proto__要是没有,那我就继续往上 在 test.__proto__.__proto__上面查找( 这里的test.__proto__.__proto__ 也就是相当于 Foo.prototype ),只要找不到,那就一直往上找。

test.__proto__.__proto__ === Foo.prototype
true

找到最后,会发现 test.__proto__.....__proto__ 的 constructor 属性 指向了 底层函数 Object () { },所以 Object () {} 也就是最后一个 __proto__  的 构造函数,也就是  test.__proto__.....__proto__ = Object.prototype。

 test.__proto__.__proto__.__proto__ === Object.prototype
true

如果直到找到了 Object.prototype 还是没有找到这个属性的话,会返回一个 undefinde。

Object.prototype 之上也是存在 __proto__ 原型对象的,但是  Object.prototype.__proto__  会返回 null ,到了这里就是原型链最顶层了。

ps1:如果我的  构造函数Bar 在声明的时候,就已经在内部定义了一个 名叫 method 的方法,但是我在原型上又定义了一个 method 方法。

function Bar() {
  this.method = function() { 
    console.log("111")
  }
}

Bar.prototype.method = function () { 
  console.log("222")
}

var test = new Bar()

这个时候,我再去 实例化对象 test 里面找 method 方法的时候,情况就不一样了。

test.method
ƒ () { console.log("111")}

可以看到,打印的是 构造函数 Bar 内部定义的 method 方法,而不是 构造函数原型 prototype 上新增的 method 方法。

ps2 :在给原型 prototype 赋值的时候,我们可以赋值任何对象,但是不能赋值某个具体的值,例如

function Foo() {}
Foo.prototype = 1; // 无效

4、hasOwnProperty

这是存在于 构造函数原型prototype 上的一个方法,主要用于判断,某个属性是 构造函数内部定义的属性,还是构造函数原型链上的属性 ,因为hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

// 修改Object.prototype
Object.prototype.bar = 1; 
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 因为 hasOwnProperty 只会在对象本身查找,不会去原型链上查找。

在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩展带来的干扰,我们来看一下例子:

// 修改 Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // 输出两个属性:bar 和 moo
}

for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i); // 这次只输出 moo。
    }
}

所以,推荐使用 在 for in 遍历对象的时候,使用hasOwnProperty,不要对代码运行的环境做任何假设

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值