原型链篇

92 阅读3分钟

3.原型/继承/构造/类篇

1.原型链的概念


JavaScript为我们提供了这个方法:Object.create(proto),这个方法生成一个空对象,并将参数proto设置为自己的原型[[Prototype]]。原型有什么用呢?简单来说,假如我们在一个对象里找某个属性或方法,没找到,那ascriptive引擎就会继续往这个对象的原型里找,再找不到就继续往这个对象原型的原型里找,直到找到或者遇到null,这个过程就是原型链啦。

2 原型prototype的概念


JavaScript里创建的每个函数都带有prototype这个属性,它指向一个对象(这个对象里包含一个constructor属性指向原来的这个函数)

3.构造函数


构造函数constructor,专门用来构造对象的,使用方法就是在构造函数前使用new指令。

4. new指令到底做了什么


  1. 生成一个新对象
  2. 为这个对象设置prototype
  3. 使用this执行构造函数
  4. 返回这个对象
// 简化版
function myNew(constructor, args) {
    const obj = {}
    Object.setPrototypeOf(obj, constructor.prototype)
    constructor.apply(obj, args)
    return obj
}
​
// 正式版
function myNew(constructor, args) {
    const obj = Object.create(constructor.prototype)
    const argsArray = Array.prototype.slice.apply(arguments) 
    const result = constructor.apply(obj, argsArray.slice(1))
    if(typeof result === 'object' && result !== null) {
        return result
    }
    return obj
}
​
// es6 进阶法
function myNew(constructor, ...args) {
    const obj = Object.create(constructor.prototype)
    const result = constructor.apply(obj, args)
    if(typeof result === 'object' && result !== null) {
        return result
    }
    return obj
}

这里我还是要继续展开讲一下,我举个例子:

const a = [1, 2, 3]
a.toString() 

大家想一下,为什么a这个数组会有一个叫toString的方法?

  1. 首先你这样声明式的创建了一个数组a,其实背后是JavaScript帮你用new Array(1, 2, 3)帮你创建的数组a
  2. 这个Array函数其实就是一个构造函数,结合我们前面讲到的各种知识,可以得出数组a的原型proto就是Array.prototype(a.__proto__ === Array.prototype)
  3. 既然数组a上没有toString这个方法,JavaScript就去它的原型Array.prototype上找
  4. 嘿,找到了
  5. 假如没找到的话,就会去Array.prototype的原型找(a.__proto__.__proto__ === Object.prototype

5. class 类


es5 写法

function User(name, age) {
    this.name = name
    this.age = age
}
​
User.prototype.grow = function(years) {
    this.age += years
    console.log(`${this.name} is now ${this.age}`)
}
User.prototype.sing = function(song) {
    console.log(`${this.name} is now singing ${song}`)
}
​
const zac = new User('zac', 28)

es6 写法

class User {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    grow(years) {
        this.age += years
        console.log(`${this.name} is now ${this.age}`)
    }
    sing(song) {
        console.log(`${this.name} is now singing ${song}`)
    }
}
​
const zac = new User('zac', 28)

class 背后的运行机制

  1. 首先创建了一个叫做User的函数
  2. 然后把class的constructor里面的代码原封不动的放到User函数里
  3. 最后将class的方法,如grow,sing放到User.prototype里

6. extends 继承

class User {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    grow(years) {
        this.age += years
        console.log(`${this.name} is now ${this.age}`)
    }
    sing(song) {
        console.log(`${this.name} is now singing ${song}`)
    }
}
​
class Admin extends User {
    constructor(name, age, address) {
        super(name, age) //to call a parent constructor
        this.address = address
    }
    grow(years) {
        super.grow(years) // to call a parent method
        console.log(`he is admin, he lives in ${this.address}`)
    }
}
​
const zac = new User('zac', 28)
1. super 关键字

为什么要调用super?

因为javascript规定了:通过继承(extends)来而的class,必须在constructor里调用super(),否则在constructor里调用this会报错!

两次调用super的意义?

  1. super(...)是用来调用父类的constructor方法(只能在constructor里这么调用)
  2. super.method(...)是用来调用父类的方法
2. react super的探究
// React内部的代码
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}
​
// 我们自己代码
class Checkbox extends React.Component {
  constructor(props) {
    super(); 
    console.log(this.props); // undefined   
  // 
}
3._prototype 原型继承的案例探究
let user = {
  name: "User",
  sing() {
    console.log(`${this.name} is singing.`)
  }
}
​
let admin = {
  __proto__: user,
  name: "Admin",
  sing() {
    this.__proto__.sing.call(this) //(*)
    console.log('calling from admin')
  }
}
​
admin.sing(); // Admin is singing. calling from admin
let admin = {
  __proto__: user,
  name: "Admin",
  sing() {
    this.__proto__.sing()
    console.log('calling from admin')
  }
}
​
admin.sing(); // User is singing. calling from admin
  1. 假如是this.__proto__.sing(),调用者是this.__proto__,相当于admin.__proto__, 也就是user对象,所以最后打印出来的是:User is singing.
  2. 假如是this.__proto__.sing.call(this),这时候我们通过call手动将调用者改为admin了,所以最后打印出来是:Admin is singing.