js prototype,[[Prototype]],__proto__,constructor,原型链详解

162 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

首先了解两个概念。(看看就行)
原型:prototype是函数的一个属性,指向一个对象,这个对象用来定义一些需要被实例继承的成员。

image.png
原型链:每个 JavaScript 对象都拥有一个[[Prototype]]对象。  获取一个对象的属性时首先会搜索其自身,然后就是它的 [[Prototype]]对象,之后再搜索此[[Prototype]]对象的 [[Prototype]]对象,直到找到这个属性或者搜索链条达到终点。这个类似链条的查找过程被称为原型链
通过以下的例子来理解(建议自己在控制台运行):

  // 定义一个构造函数(使用new运算符实例化对象的函数可以称为构造函数,实际就是一个函数)
  function Student (name, age) {
    this.name = name
    this.age = age
  }
  // 在函数的原型对象上添加一个函数
  Student.prototype.study = function () {
    console.log('study hard and make progress every day')
  }
  // 使用new操作符获得这个函数的实例对象
  let s1 = new Student('jack', 3)
  console.log(s1)
  s1.study()
  /** 打印的结果,实例本身并没有study
  Student {name: 'jack', age: 3}
    age: 3
    name: "jack"
    [[Prototype]]: Object
      study: ƒ ()
      constructor: ƒ Student(name, age)
      [[Prototype]]: Object
        constructor: ƒ Object()
        hasOwnProperty: ƒ hasOwnProperty()
        isPrototypeOf: ƒ isPrototypeOf()
        propertyIsEnumerable: ƒ propertyIsEnumerable()
        toLocaleString: ƒ toLocaleString()
        toString: ƒ toString()
        valueOf: ƒ valueOf()
        __defineGetter__: ƒ __defineGetter__()
        __defineSetter__: ƒ __defineSetter__()
        __lookupGetter__: ƒ __lookupGetter__()
        __lookupSetter__: ƒ __lookupSetter__()
        __proto__: (...)
        get __proto__: ƒ __proto__()
        set __proto__: ƒ __proto__()
  study hard and make progress every day
  */

指南:实例对象的[[Prototype]]属性指向函数的prototype函数。

这里为什么能访问到study呢?其实,当你访问s1 中的一个属性,浏览器首先会查看s1 中是否存在这个属性。如果 s1 不包含属性信息,那么浏览器会在 s1 的 [[Prototype]] 中进行查找 (同 Student.prototype). 如属性在 s1 的 [[Prototype]] 中查找到,则使用这个属性。

否则,如果 s1 中 [[Prototype]] 不具有该属性,则检查s1 的 [[Prototype]] 的  [[Prototype]] 是否具有该属性。

如果属性不存在 s1 的 [[Prototype]] 的  [[Prototype]] 中, 那么就会在s1 的 [[Prototype]] 的  [[Prototype]] 的  [[Prototype]] 中查找。然而,这里存在个问题:s1 的 [[Prototype]] 的  [[Prototype]] 的  [[Prototype]] 其实不存在。在 [[Prototype]] 的整个原型链被查看之后,这里没有更多的 [[Prototype]] , 浏览器断言该属性不存在,并给出属性值为 undefined 的结论。

注意区分这两个属性:prototype 是函数的属性,每个函数都有prototype属性;[[Prototype]]是实例对象的原型,本身也是一个属性,指向了构造函数的prototype 属性。

在new的过程中,实际上做了 s1.[[Prototype]] = Student.prototype 这一步, 但是由于 [[Prototype]] 不能直接访问,官方推荐这样写:Object.create(Student.prototype);另外 s1.proto = Student.prototype也是可以执行,但是不推荐。简而言之,prototype[[Prototype]]都指向了同一个对象。

注意:对象的原型在 JavaScript 语言标准中用 [[Prototype]] 表示,可以使用ES6的 Object.getPrototypeOf() 获取对象的原型。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 s1.__proto__ 和 s1.__proto__.__proto__,看看代码中的原型链是什么样的!__proto__是弃用的,尽量不在生产环境使用。

接着讲 prototype 有什么属性和各自的作用

image.png

首先study是我们自己添加的函数。
constructor 属性指向了构造函数本身,有两个用途:
1、假如我们现在只有对象(obj),没有构造函数,可以用new obj.constructor()创建另一个对象实例,但是一般不用。2、获取某个对象的构造器名字,可以用obj.constructor.name。
[[Prototype]] 指向了用于创建函数对象的构造函数的prototype属性(有点拗口),例如Student函数是Object构造函数创建的,也就是指向了Object.prototype;作用:用于继承属性或者方法,例如:s1拥有Object.prototype的方法。

总结一波:prototype通俗的讲就是函数与生俱来的属性;prototype.constructor指向了函数本身;[[Prototype]]是对象的属性,指向了其构造函数的prototype属性;原型链就是一连串的[[Prototype]],无限套娃;此外__proto__可以说是旧版的[[Prototype]],官方申明已废弃。

现在的__proto__指的是Object.prototype的 __proto__  属性,它是一个访问器属性(一个 getter 函数和一个 setter 函数), 暴露了通过它访问的对象的内部[[Prototype]](一个对象或 null)。

image.png

也就是说s1.__proto__拿到的就是s1.[[Prototype]]。

这几个概念确实容易混,打开控制台输出看看好一点。最后就是很多老的博客把__proto__当做是对象的原型,新的浏览器是[[Prototype]]代表对象的原型。原型链也是在没有了[[Prototype]]属性的时候就没了。还有原型链的末端是null。

有问题欢迎留言