原型与原型链

131 阅读5分钟

一、 生产对象的两种方式

  • 类的方式生产对象:先创建类,然后通过类的实例化(new一个对象)创建对象,大多数编程语言都是基于类来生产对象的,如java、pathyon、php等
  • 原型方式生产对象:先有一个对象A,然后通过A产生对象B(类似克隆的方式),A就是B的原型对象。js最初就是通过原型方式来创建对象的。
const obj = {}
console.log(obj.__proto__); // __proto__方式找到原型对象:[Object: null prototype] {}

在js中,无论对象是如何书写的,该对象都有自己的原型对象,即它一定是由某个对象克隆而来的

二、最初的js创建对象的原理

const person = {
 arms: 2,
 legs: 2,
 walk(){
     console.log('walking');
 }
}
// 通过Object.create()方法创建对象,并且新增两个属性,可以指定原型对象为person
const john = Object.create(person, {
    name: {
        value: 'john',
        enumerable: true,
    },
    age: {
        value: 18,
        enumerable: true,
    }
})
console.log(john.__proto__); // { arms: 2, legs: 2, walk: [Function: walk] }
console.log(john.__proto__ === person); // true

三、创建对象的演进(构造函数)

后面js创始人布兰登艾奇,对js做了一些改进,使js与java足够相似,添加了this、new等关键字,使js看上去像是基于类来生产对象,实质还是基于原型,由于当时没有class关键字,所以通过函数来模拟类,即构造函数,为了和普通函数区分,构造函数通常首字母大写。

function Phone(name, price){
    this.name = name
    this.price = price
}
Phone('华为', 3999)

构造函数既可以像普通函数一样调用,还可以通过new关键字调用(函数二义性),但是与普通函数的执行机制不同,new一个构造函数会经历以下4个步骤

  1. 创建一个简单的js空对象,即{}
  2. 将this指向创建的对象
  3. 执行构造函数内部的代码(为对象增加实例属性和实例方法)
  4. 将创建的对象作为返回值返回
function Phone(name, price){
    // 1. 创建一个普通对象: const instance = new Object() 或者const obj = {}
    // 2. this = obj
    // 3. 执行构造函数内部代码
    this.name = name // obj = { name: '小米'}
    this.price = price // obj = { name: '小米', price: '3999'}
    // 4. 将创建的对象作为返回值返回
    // return instance
}
const xiaomi = new Phone('小米', 3999)
console.log(xiaomi); // Phone { name: '小米', price: 3999 }

四、通过类创建对象

ES6新增了class关键字,可以通过类来创建对象:

class Phone {
    constructor(name, price){
        this.name = name
        this.price = price
    }
}
const xiaomi = new Phone('小米', 3999)
console.log(xiaomi); // Phone { name: '小米', price: 3999 }

不管js如何模拟面向对象的特性,哪怕是新增了class关键字,但是底层仍是基于原型创建对象的语言,这一点以后也不会改变。

五、实例对象、原型对象、构造函数三者关系

2024-05-07-090308.png

function Phone(){} // 基础构造函数
const p = new Phone() // 产生实例对象
console.log(p.__proto__ === Phone.prototype); // true 实例对象的__proto__属性和构造函数的prototype属性相等的,即都是原型对象
console.log(p.constructor === Phone); // 实例对象没有构造函数,就会去他的原型对象上寻找,所以也为true
console.log(p.constructor === Phone.prototype.constructor) // true

不仅自定义的构造函数存在这种三角关系,内置的构造函数也符合这种关系,比如数组也是对象,通过new Array()产生

console.log([].__proto__ === Array.prototype);
console.log([].constructor === Array);
console.log([].constructor === Array.prototype.constructor);
// 1.为原始数据类型的包装对象
console.log(1..__proto__ === Number.prototype);
console.log(1..constructor === Number);
console.log(1..constructor === Number.prototype.constructor);

可以看出js中其实一切皆对象,不仅引用数据类型(对象),原始数据类型(string\number\boolean等),背后也是通过new得到的

六、原型链

2022-08-24-040315.png

  • JS 中的对象大体上分为两大类:普通对象(红色) 和 构造器对象(紫色)
  • 无论是 普通对象 还是 构造器对象,都会有自己的原型对象,通过 proto 这个隐式属性,就能找到自己的原型对象,并且一直向上找,最终会到达 null.
  • 普通对象 和 构造器对象 的区别在于是否能够实例化,构造器对象可以通过 new 的形式创建新的实例对象,这些实例对象的原型对象一直往上找最终仍然是到达 null.
  • 只有 构造器对象 才有 prototype 属性,其 prototype 属性指向实例对象的原型对象
  • 所有 构造器对象 的原型对象均为 Function.prototype
  • 无论是 普通对象 还是 构造器对象,最终的 constructor 指向 Function,而 Function 的 constructor 指向自己本身。
  • Object 这个 构造器对象 比较特殊,实例化出来的对象的原型对象直接就是 Object.prototype,而其他的构造器对象,其实例对象的原型对象为对应的 xxx.prototype,再往一层才是 Object.prototype.

简而言之,就是任何对象都有自己的原型对象,通过__proto__属性找到原型对象,并且会一直向上查找,最终找到null

function Phone(){}
const p = new Phone()
console.log(p.__proto__) // {}
console.log(p.__proto__.__proto__); // [Object: null prototype] {} 即Object.prototype
console.log(p.__proto__.__proto__.__proto__); // null

七、原型链的应用

1. 原型对象上挂载通用的方法

function Phone(name, price) {
    this.name = name
    this.price = price
}
Phone.prototype.showPrice = function(){
    console.log(`我是${this.name}手机,售价为${this.price}`);
}
const huawei = new Phone('华为', 4999)
const xiaomi = new Phone('小米', 3999)
huawei.showPrice()
xiaomi.showPrice()

再举个例子,我们使用vue框架时,会在main.js中,给Vue构造函数的原型对象添加通用的接口等

// 通过将$apiList挂载到原型对象上,通过this.$apiList就可以访问apiList中定义的接口
Vue.prototype.$apiList = apiList;
new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

2. 原型链相关方法

2.1 Object.getPrototypeOf( )

该方法用于查找一个对象的原型对象

function Phone() {}
const p = new Phone()
console.log(Object.getPrototypeOf(p) === p.__proto__);
console.log(Object.getPrototypeOf(p) === Phone.prototype);

2.2 instanceof 操作符

判断一个对象是否是一个构造函数的实例。如果是返回 true,否则就返回 false

function Phone(){}
const p = new Phone()
console.log(p instanceof Phone); // true
console.log(p instanceof Array); // false
console.log([] instanceof Array); // true
console.log([] instanceof Object);// true

2.3 isPrototypeOf( )

主要用于检测一个对象是否是一个另一个对象的原型对象,如果是返回 true,否则就返回 false

function Phone(){}
const p = new Phone()
console.log(Phone.prototype.isPrototypeOf(p)); // true
console.log(Array.prototype.isPrototypeOf(p)); // false
console.log(Array.prototype.isPrototypeOf([])); // true

2.4 hasOwnProperty( )

判断一个属性是定义在对象本身上面还是从原型对象上面继承而来的。 如果是本身的,则返回 true,如果是继承而来的,则返回 false

const person = {
    arms: 2,
    legs: 2,
    walk(){
        console.log('walking');
    }
}
// 通过Object.create()方法创建对象,可以指定原型对象为person
const john = Object.create(person, {
    name: {
        value: 'john',
        enumerable: true,
    },
    age: {
        value: 18,
        enumerable: true,
    }
})
console.log(john.hasOwnProperty("name")); // true
console.log(john.hasOwnProperty("arms")); // false