乐趣区

浅谈构造函数原型与原型链

原型与原型链

原型对象

构造函数可以通过 new 的方法来创建一个实例,而每一个构造函数都有一个 prototype 的属性指向一个原型对象。原型对象中所有的属性与方法都可以被该函数所创建的实例所共享。

原型链

每个被构造函数创建出来的实例都有一个隐式的属性__proto__,该属性指向构造函数的 prototype,即原型对象。浏览器在取对象中的方法时,会先在对象中寻找是否存在该方法,如果没有则通过__proto__到其原型对象上寻找,以此一层层通过__proto__,直至为 null。

原型链是基于 proto 形成的,继承是通过 prototype 实现的

Object,Function,__proto__,prototype

在 JS 中,万物皆对象,所以函数(方法)也是一个对象。

所有的对象都有一个根源 Object.prototype,Object.prototype 只是一个普通对象

根源的原型对象是 null

但是 null 却不是一个对象,虽然用 typeof 验证 null 为 object,这个是 JS 的 bug。

(typeof Object.prototype) === object;//true
Object.prototype.__proto__=== null;//true
Object.prototype.prototype === undefied;//true

特例:__Object 和 Function 既是对象,又是函数,两者内部同时含有 proto 和 prototype 属性,他们关系较为复杂__Function.prototype 指向“__内置函数__”,而 Object.prototype 指向“__根源对象__”

Object.__proto__ === Function.prototype //true
Object.__proto__ === Function.__proto__//true
Object.prototype === Function.prototype.__proto__ // true
// 因此
Function instanceof Object //true
Object instanceof Function //true

x instanceof y,当 y 的原型对象在 x 的原型链之上,返回 true,否则返回 false

构造函数与普通函数

创建方式相同

构造函数也是个普通函数,但是一般来说,构造函数会采用首字母大写的方式。

调用方式不同,目的不同

普通函数:直接调用,返回值为函数调用的之后 return 的值,无 return 则为空;
构造函数:需要用 new 调用,以创建实例的方式来调用,目的是创建一个指向构造函数的原型的一个实例对象。

构造函数调用创建实例的时候内含多步骤

这个问题可以换个角度说,就是当我们再 new 一个对象的时候,内部发生了什么。
1、首先需要创建一个空对象。
2、将空对象的__proto__指向构造函数的 prototype(构造函数的原型对象),以此获得原型的方法与属性
3、绑定 this 的指向
4、返回新对象
在文章下方会手动实现一个简易版的 new 方法

构造函数用 this 构造内部方法与属性

function Cat(name, age) {
    this.age = age
    this.name = name
    this.run = run
    function run() {}
}
let cat = new Cat('米粒', '6 个月')

构造函数所创建的实例都可以用 instanceof

可以用来验证该实例是否由验证的构造函数所创建的。原理就是去查找实例的__proto__是否与构造函数的 prototype 所指向的原型对象是同一个。
文章下方将会手写一个简略版的 instanceof

手写 new

当我们再 new 一个对象的时候,内部发生了什么。

1、首先需要创建一个空对象。
2、将空对象的__proto__指向构造函数的 prototype(构造函数的原型对象),以此获得原型的方法与属性
3、绑定 this 的指向
4、返回新对象

function Cat(name, age) {
    this.age = age
    this.name = name
    this.run = run
    function run() {}
}

function _new() {
    // 
    let newObject = new Object()
    // arguments 是传入函数的参数,因为是类数组的形式, 无法直接使用 shift 方法
    // 所以使用 ES6 的新特性进行转换
    // Array.prototype.shift.apply(arguments)用数组原生方法也是可以的
    arguments = Array.from(arguments)
    // 取出构造函数
    let Constructor =  arguments.shift()
    //  获得构造函数的原型对象上的方法与属性
    newObject.__proto__ = Constructor.prototype
    // 绑定 this 的指向
    Constructor.apply(newObject, arguments)
    return newObject
}

cat = _new(Cat, '米粒', '6 个月')
console.log(cat)
// Cat {age: '6 个月', name: '米粒', run: [Function: run] }

手写 instanceof

原理:查找实例的__proto__是否与构造函数的 prototype 所指向的原型对象是同一个。

function instance_of(L, R) {
    const R_prototype = R.prototype
    let L_proto = L.__proto__
    let flage = false
    while(L_proto !== null) {if (L_proto === R_prototype) {
            flage = true
            break
        }
        L_proto = L_proto.__proto__; 
    }
    return flage
}

// 使用上方的_new 方法来创建实例
cat = _new(Cat, '米粒', '6 个月')
console.log(cat)

console.log(cat instanceof Cat) // true
console.log(instance_of(cat, Cat)) // true
console.log(instance_of(cat, Object)) // true
console.log(instance_of(cat, {})) // false
console.log(instance_of(cat, Boolean)) // false

退出移动版