函数的 prototype

之前学到过,通过 new Function() 的方式来创建一个对象。

假如 Function.prototype 为一个对象,那么使用 new 创建一个对象时将会把 Function.prototype 作为新对象的 [[Prototype]]

$$jsdemo$$
$$edit$$
const animal = {
    sleep() {
        alert("睡觉")
    },
}

function Cat() {
    this.name = "猫猫"
}
Cat.prototype = animal

const cat = new Cat()
// 隐性执行了 cat.__proto__ = Cat.prototype
cat.sleep() // 睡觉

alert(cat.__proto__ === animal) // true

函数有一个默认的 prototypeprototype 为一个对象且只有一个属性 constructor指向函数自身。

$$jsdemo$$
$$edit$$
function Cat() {}

console.log(Cat.prototype) // {constructor: Cat}
console.log(Cat.prototype.constructor === Cat) // true

$$warning

不建议使用 Function.prototype = { ... } 这种写法,因为会将函数自身的 prototype 完全覆盖掉。推荐使用 Function.prototype.xxx = xxx 的写法。

$$

$$jsdemo$$
$$edit$$
function Cat(name) {
    this.name = name
}

// 不推荐
Cat.prototype = {
    sayName() {
        alert(`我叫${this.name}`)
    },
}

const cat = new Cat("喵喵")
// cat 的 constructor 消失了
// 这里打印的是更上一层原型 Object 的 constructor
console.log(cat.constructor) // Object() { }

function Dog(name) {
    this.name = name
}

// 推荐
Dog.prototype.sayName = function sayName() {
    alert(`我叫${this.name}`)
}

const dog = new Dog("狗狗")
// dog 的 constructor 仍然可以找到
console.log(dog.constructor) // Dog() { }

$$tip

构造函数中的方法、属性尽量写到 prototype 中,便能得到上章提到的原型继承的好处。

$$

$$jsdemo$$
$$edit$$
function Cat(name) {
    this.name = name

    // 不推荐
    this.sayName = function () {
        alert(`我叫${this.name}`)
    }
}

const cat = new Cat("喵喵")
const cat2 = new Cat("丑丑")
const cat3 = new Cat("加菲")

console.log(cat)
console.log(cat2)
console.log(cat3)

如控制台看到的一样,每个实例都有一个重复的 sayName 方法。 image

以下是把 sayName 定义到了 prototype 中。

$$jsdemo$$
$$edit$$
function Cat(name) {
    this.name = name
}

// 推荐
Cat.prototype.sayName = function () {
    alert(`我叫${this.name}`)
}

const cat = new Cat("喵喵")
const cat2 = new Cat("丑丑")
const cat3 = new Cat("加菲")

console.log(cat)
console.log(cat2)
console.log(cat3)

实例共用了原型上的 sayName 方法。 image

instanceof

我们一定用过 instanceof 这个操作符,这个操作符的原理便是判断函数的 prototype 是否存在于实例的原型链上。

比如以下的实例。

$$jsdemo$$
$$edit$$
function Cat() {
    this.name = "猫猫"
}

function Garfield() {
    this.name = "加菲猫"
}
Garfield.prototype.__proto__ = Cat.prototype

const garfield = new Garfield()

alert(garfield instanceof Garfield) // true
// 因为 garfield.[[Prototype]] === Garfield.prototype
alert(garfield.__proto__ === Garfield.prototype) // true

alert(garfield instanceof Cat) // true
// 因为 garfield.[[Prototype]].[[Prototype]] === Cat.prototype
alert(garfield.__proto__.__proto__ === Cat.prototype) // true

function Dog() {
    this.name = "狗狗"
}
alert(garfield instanceof Dog) // false

原生类型的 prototype

当我们创建一个对象、数组、函数等等原生对象时,相当于调用了对应的构造函数。

$$jsdemo$$
$$edit$$
// 以下是等价的
const obj = {}
const obj2 = new Object()

alert(obj.__proto__ === Object.prototype) // true

// 以下是等价的
const arr = []
const arr2 = new Array()

alert(arr.__proto__ === Array.prototype) // true

这意味着我们很多自带的方法其实是在 prototype 中实现的。

$$jsdemo$$
$$edit$$
// 比如数组常用的 join 方法就在 prototype 中
const arr = [1, 2, 3]

alert(arr.join === Array.prototype.join) // true
alert(arr.join("-")) // 1-2-3

这表示我们可以自己实现一些方法,并挂载到 prototype 中。

$$jsdemo$$
$$edit$$
String.prototype.toNumber = function () {
    return Number(this)
}

const n = "123".toNumber()

alert(n) // 123
alert(typeof n) // number

练习

  1. 补全以下代码,使用到 prototype ,并使其能正常运行。
function MathArray(...arr) {
    this.arr = arr
}

// 补全这里
// MathArray.prototype...


const arr = new MathArray(5, -10, 20, 12)

alert(arr.getMin()) // -10
alert(arr.getMax()) // -20

$$answer

$$jsdemo$$
$$edit$$
function MathArray(...arr) {
    this.arr = arr
}

// 补全这里
MathArray.prototype.getMin = function () {
    return this.arr.reduce((a, b) => (a < b ? a : b))
}
MathArray.prototype.getMax = function () {
    return this.arr.reduce((a, b) => (a > b ? a : b))
}

const arr = new MathArray(5, -10, 20, 12)

alert(arr.getMin()) // -10
alert(arr.getMax()) // -20

$$

  1. 实现一个 myInstanceof 函数,接收两个参数实例和构造函数,判断实例是否是构造函数的实例。
function myInstanceof(obj, func) {
    // 补全这里
}

function Cat() {
    this.name = "猫猫"
}

function Garfield() {
    this.name = "加菲猫"
}
Garfield.prototype.__proto__ = Cat.prototype

const garfield = new Garfield()

alert(myInstanceof(garfield, Garfield)) // true
alert(myInstanceof(garfield, Cat)) // true

function Dog() {
    this.name = "狗狗"
}

alert(myInstanceof(garfield, Dog)) // false

$$answer

$$jsdemo$$
$$edit$$
function myInstanceof(obj, func) {
    if (obj.__proto__ === func.prototype) {
        // 找到原型
        return true
    }

    if (obj.__proto__ === null) {
        // 到顶
        return false
    }

    return myInstanceof(obj.__proto__, func)
}

function Cat() {
    this.name = "猫猫"
}

function Garfield() {
    this.name = "加菲猫"
}
Garfield.prototype.__proto__ = Cat.prototype

const garfield = new Garfield()

alert(myInstanceof(garfield, Garfield)) // true
alert(myInstanceof(garfield, Cat)) // true

function Dog() {
    this.name = "狗狗"
}

alert(myInstanceof(garfield, Dog)) // false

$$

  1. 补全以下代码,使得数组自带 getSum 方法对所有数字求和。
// 补全以下代码

const arr = [5, 10, 20]
alert(arr.getSum()) // 35

$$answer

$$jsdemo$$
$$edit$$
Array.prototype.sum = function () {
    return this.reduce((a, b) => a + b)
}

const arr = [5, 10, 20]
alert(arr.getSum()) // 35

$$