• constructor、prototype、__proto__、[[Prototype]]及原型链


    一、基础入门

    1、对象

    在JS中,万物皆对象,对象又分为普通对象和函数对象,其中 Object、Function 为 JS 自带的函数对象。

    let obj1 = {}
    let obj2 = new Object()
    let obj3 = new fun1()
    
    function fun1(){}
    let fun2 = function(){}
    let fun3 = new Function('some', 'console.log(some)')
    
    // JS自带的函数对象
    console.log(typeof Object) // function
    console.log(typeof Function) // function
    
    // 普通对象
    console.log(typeof obj1) // object
    console.log(typeof obj2) // object
    console.log(typeof obj3) // object
    
    // 函数对象
    console.log(typeof fun1) // function
    console.log(typeof fun2) // function
    console.log(typeof fun3) // function

    凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象,Function、Object 是通过 new Function() 创建的

    2、构造函数

    function Foo(name, age){
      // this指向Foo
      this.name = name
      this.age = age
      this.class = 'class'
      // return this // 默认有这一行
    }
    // Foo的实例
    let f = new Foo('dylan', 25)

    每个实例都有一个 constructor(构造函数)属性,该属性指向对象本身

    f.constructor === Foo // true

    构造函数本身就是一个函数,与普通函数没有任何区别,不过为了规范一般将其首字母大写。构造函数和普通函数的区别在于,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。

    JS本身不提供一个 class 实现。(在ES6中引入了 class 关键字,但至是语法糖,JavaScript仍然是基于原型的)

    3、构造函数扩展

    • let a = {} 其实是 let a = new Object() 的语法糖
    • let a = [] 其实是 let a = new Array() 的语法糖
    • function Foo(){...} 其实是 var Foo = new Function(...)
    • 可以使用 instanceof 判断一个函数是否为一个变量的构造函数

    4、Symbol是构造函数吗

    Symbol 是基本数据类型,它并不是构造函数,因为它不支持 new Symbol() 语法,我们直接使用 Symbol() 即可

    let name = Symbol('name')
    let name1 = new Symbol("name1") // Symbol is not a constructor

    但是,Symbol() 可以获取到它的 constructor 属性

    let name = Symbol('name')
    console.log(name.constructor) // ƒ Symbol() { [native code] }

    这个 constructor 实际上是 Symbol 原型上的,即

    console.log(Symbol.prototype.constructor) // ƒ Symbol() { [native code] }

    对于Symbol,你还需要了解以下知识点:

    Symbol() 返回的 symbol 值是唯一的

    console.log(Symbol("name") === Symbol("name")) // false

    可以通过 Symbol.for(key) 获取全局唯一的 symbol

    console.log(Symbol.for("name") === Symbol.for("name")) // true

    它从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中

    Symbol.iterator:返回一个对象的迭代器

    // 实现可迭代协议,使迭代器可迭代:Symbol.interator
    function createIterator(items){
      let i = 0
      return {
        next: function(){
          let done = (i >= items.length)
          var value = !done ? items[i++] : undefined
          return {
            done,
            value
          }
        },
        [Symbol.iterator]: function(){
          return this
        }
      }
    }
    
    const iterator = createIterator([1,2,3])
    console.log(iterator) // {next: ƒ, Symbol(Symbol.iterator): ƒ}
    console.log([...iterator]) // [1,2,3]

    Symbol.toPrimitive:将对象转换成基本数据类型

    // Symbol.toPrimitive 来实现拆箱操作(ES6之后)
    let obj = {
      valueOf: () => {console.log("valueOf"); return {}},
      toString: () => {console.log("toString"); return {}}
    }
    obj[Symbol.toPrimitive] = () => {
      console.log("toPrimitive")
      return "hello"
    }
    console.log(obj + "")
    // toPrimitive
    // hello

    Symbol.toStringTag:用于设置对象的默认描述字符串值

    // Symbol.toStringTag 代替 [[class]] 属性(ES5开始)
    let o = {
      [Symbol.toStringTag]: "MyObject"
    }
    console.log(o + "") // [object MyObject]

    5、constructor 的值是只读的吗?

    对于引用类型来说 constructor 属性值是可以修改的,但是对于基本类型来说是只读的。

    引用类型

    function Parent(){
      this.value = "parent"
    }
    function Child(){}
    Child.prototype.constructor = Parent // 原型链继承中,对constructor重新赋值
    
    let child = new Child() // 创建一个新实例
    
    console.log(child.__proto__ === Child.prototype) // true
    console.log(child.__proto__.constructor === Parent) // true

    这时,child原型上的构造函数是 Parent     这说明,依赖一个引用对象的 constructor 属性,并不是安全地

    基本类型

    function An(){}
    let an = 1
    an.constructor = An
    console.log(an.constructor) // ƒ Number() { [native code] }

    这是因为:原生构造函数是只读的。

    注意:null 和 undefined 是没有 constructor 属性的

    二、原型

     在JS中,每个对象都有自己的原型。当我们访问对象的属性和方法时,JS会先访问对象本身的方法和属性。如果对象本身不包含这些属性和方法,则访问对象对应的原型。

    // 构造函数
    function Foo(name){
      this.name = name
    }
    Foo.prototype.alertName = function(){
      alert(this.name)
    }
    
    // 创建实例
    let f = new Foo('some')
    f.printName = function(){
      console.log(this.name)
    }
    
    // 测试
    f.printName()
    f.alertName()

    1、prototype

      所有函数都有一个 prototype (显式原型)属性,属性值也是一个普通的对象。对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。

    但有一个例外:Function.prototype.bind() ,它并没有 prototype 属性

    Function.prototype.bind() // ƒ () { [native code] }

    当我们创建一个函数时

     prototype 属性就被自动创建了

    从上面这张图可以发现,Foo 对象有一个原型对象,Foo.prototype,其上有两个属性,分别是 constructor 和 __proto__ 

    构造函数 Foo 有一个指向原型的指针,原型 Foo.prototype 有一个指向构造函数的指针 Foo.prototype.constructor,这就是一个循环引用,即

    Foo.prototype.constructor === Foo  // true

    2、__proto__

    每个实例对象(object)都有一个隐式原型属性(称之为 __proto__)指向了创建该对象的构造函数的原型。也就是指向了函数的 prototype 属性

    function Foo(){}
    let foo = new Foo()
    
    foo.__proto__ === Foo.prototype  // true

    当 new Foo() 时,__proto__ 被自动创建,即

    3、[[Prototype]]

    [[Prototype]] 是对象的一个内部属性,外部代码无法直接访问

    遵循 ECMAScript标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型

    4、注意

      __proto__ 属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()  。

      通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性,这种行为在每个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性会对性能影响非常严重,并且性能消耗的时间也不是简单的花费 obj.__proto__ = ... 语句上,它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]] 。相反,创建一个新的且可以继承 [[Prototype]] 的对象,推荐使用 Object.create()

    function An(){}
    var an = new An()
    var anran = Object.create(an)

    这里 anran 是一个新的空对象,有一个指向对象 an 的指针 __proto__

    5、new的实现过程

    • 新生成了一个对象
    • 链接到原型
    • 绑定this
    • 返回新对象
    function new_object(){
      // 创建一个空的对象
      let obj = new Object()
      // 获得构造函数
      let Con = [].shift.call(arguments)
      // 链接到原型(不推荐使用)
      obj.__proto__ = Con.prototype
      // 绑定 this,执行构造函数
      let result = Con.apply(obj, arguments)
      // 确保 new 出来的是个对象
      return typeof result === 'object' ? result : obj
    }

    优化 new 实现

    function create(){
      // 1、获得构造函数,同时删除 arguments 中第一个参数
      let Con = [].shift.call(arguments)
      // 2、创建一个空的对象并链接到原型,obj可以访问构造函数原型中的属性
      let obj = Object.create(Con.prototype)
      // 3、绑定 this 实现继承,obj可以访问到构造函数中的属性
      let ret = Con.apply(obj, arguments)
      // 4、优先番红花构造函数返回的对象
      return ret instanceof Object ? ret : obj
    }

    6、总结

    • 所有的引用类型(数组、对象、函数)都有对象特性,即可自由扩展属性(null除外)
    • 所有的引用类型,都有一个 __proto__ 属性,属性值是一个普通的对象,该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null没有原型,并作为这个原型链中的最后一个环节
    • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 __proto__ (即它的构造函数的 prototype) 中寻找

    三、原型链

       每个对象都拥有一个原型对象,通过 __proto__ 指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null,这种关系被称为原型链。根据定义,null 没有原型,并作为这个原型链中的最后一环。

     原型链的基本思想是利用原型,让一个引用类型继承另一个引用类型的属性及方法

    // 构造函数
    function Foo(name){
      this.name = name
    }
    // 创建实例
    let f = new Foo('dylan')
    // 测试
    console.log(f.toString()) // [object Object]
    console.log(f.__proto__) // constructor: ƒ Foo(name)
    console.log(f.__proto__.__proto__) // constructor: ƒ Object()

    f.__proto__ === Foo.prototype ,Foo.prototype 也是一个对象,也有自己的 __proto__ 指向 Object.prototype ,找到 toString()方法

    也就是

    Function.__proto__.prototype === Object.prototype

    下面是原型链继承的例子

    function Elem(id){
      this.elem = document.getElementById(id)
    }
    Elem.prototype.html = function(val){
      let elem = this.elem
      if(val){
        elem.innerHTML = val
        return this // 链式操作
      }else{
        return elem.innerHTML
      }
    }
    
    Elem.prototype.on = function(type, fn){
      let elem = this.elem
      elem.addEventListener(type, fn)
    }
    
    let div1 = new Elem('div1')
    div1.html("<p>hello</p>").on("click", function(){
      alert("click")
    })

     四、总结

    • Symbol 是基本数据类型,并不是构造函数,因为它不支持语法 new Symbol(),但其原型上用于 constructor 属性,即 Symbol.prototype.constructor
    • 引用类型 constructor 是可以修改的,但对于基本类型来说它是只读的,null 和 undefined 没有 constructor 属性
    • __proto__ 是每个实例对象都有的属性,prototype 是其构造函数的属性,在实例上并不存在,所以这两个并不一样,但 foo.__proto__ 和 Foo.prototype 指向同一个对象
    • __proto__ 属性在 es6 时被标准化,但因为性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
    • 每个对象都拥有一个原型对象,通过 __proto__ 指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层向上,最终指向null,这就是原型链
    • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的原型中寻找,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性/方法或到达原型链的末尾(null)

    1

  • 相关阅读:
    log4cpp
    互斥锁封装
    Educational Codeforces Round 37-F.SUM and REPLACE (线段树,线性筛,收敛函数)
    Codeforces 920E-Connected Components? (set,补图,连通块)
    Persistent Bookcase CodeForces
    P4390 [BOI2007]Mokia 摩基亚 (CDQ解决三维偏序问题)
    P3157 [CQOI2011]动态逆序对 (CDQ解决三维偏序问题)
    CDQ 分治解决和点对有关的问题
    洛谷 P2163 [SHOI2007]园丁的烦恼 (离线sort,树状数组,解决三维偏序问题)
    洛谷 P3469 [POI2008]BLO-Blockade (Tarjan,割点)
  • 原文地址:https://www.cnblogs.com/haishen/p/12055434.html
Copyright © 2020-2023  润新知