likes
comments
collection
share

原型是什么——阿尼亚知道🔥🔥🔥

作者站长头像
站长
· 阅读数 22

面试官:介绍一下原型和原型链

我:(这题我背过)在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,prototype是一个对象,它就是该构造函数的原型......

面试官:解释一下

我:emmmm

1、为什么要学习原型

因为面试要问(不是)。原型是JS语言的一个核心概念,原型是 为了避免重复造轮子,让JavaScript对象拥有封装和继承特性。封装是指让使用对象的人不考虑内部实现,只考虑功能使用把内部的代码保护起来,只留出一些api接口供用户使用。继承就是为了代码的复用,从父类上继承出一些方法和属性,子类也有自己的一些属性。

2、看图说话

原型

原型的概念涉及到三方的关系:构造函数、原型对象、实例对象

原型是什么——阿尼亚知道🔥🔥🔥

我们来看图说话:构造函数.prototype = 原型对象;原型对象.constructor = 构造函数;New 构造函数 = 实例对象;实例对象.__proto__ = 原型 。

用动画片《间谍过家家》来表述就是:构造函数就是母亲“约尔太太”,原型对象是家里的男主人“黄昏”,实例对象就是“阿尼亚”。约尔太太称呼黄昏为丈夫(prototype),黄昏称呼约尔太太妻子(constructor),约尔太太生(new)了阿尼亚,阿尼亚的爸爸(__proto__)是黄昏。

原型是什么——阿尼亚知道🔥🔥🔥

原型链

原型链简单说就是把原型“链”起来。当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__上查找,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

原型是什么——阿尼亚知道🔥🔥🔥

3、两道手写题(new, apply)

接下来我们来看两道原型过程中涉及到的高频手写题。

new

构造函数“new出”一个实例对象 (换句话说,约尔太太“生出”阿尼亚)

let _new = function(constructor,args){
    let obj = {};//创造一个阿尼亚,
    obj.__proto__ = constructor.prototype;//给阿尼亚配置一个爸爸:黄昏
    let result = constructor.apply(obj,args);//给阿尼亚配置一个妈妈:约尔太太
    return result instanceof Object? result:obj;//约尔太太带着孩子来的吗?返回带来的孩子:返回阿尼亚。
}

apply

把函数放在对象里执行一遍再删除。

Function.prototype._apply = function(obj,args){
    obj = obj||window;
    let key = Symbol();
    obj[key] = this;//把函数放在对象里
    let res = obj[key](...args);//执行一遍这个函数
    delete obj[key];//把这个函数在对象里删除
    return res;
}

4、继承

继承是指子函数继承了母函数和父原型的特性。

我们现实生活中的继承,一般是指孩子继承了父亲和母亲的一些特点或者产业。同样的在JS世界里,继承是指子函数继承了母函数父原型的特性。下面我们来具体看看继承的几种方法。

A. 原型链继承:

概念:让子函数.prototype等于母函数的实例对象,进而使子函数的实例对象通过__proto__不断向上找寻相关属性,直到找到为止。

缺点:如果有属性是引用类型,一旦某个实例修改了这个属性,所有实例都会受到影响。

原型是什么——阿尼亚知道🔥🔥🔥

function Parent() { 
    this.actions = ['parentName', 'play'] 
} 
Parent.prototype.eat = function () { console.log(`${this.name} - eat`); }; 
function Child() {} 
Child.prototype = new Parent() 
let child1 = new Child() 
let child2 = new Child() 
child1.actions.pop() 
console.log(child1.actions) 
console.log(child2.actions) 

B. 盗用构造函数继承:

概念:把母函数放在子函数里面apply一下,进而将母函数的属性赋给子函数。

缺点:子函数的实例对象无法复用属性,浪费内存;不能继承到母函数原型上的属性。

原型是什么——阿尼亚知道🔥🔥🔥

function Parent(name, actions) {
    this.actions = actions; 
    this.name = name; 
    this.eat = function() { console.log(name) }
} 
function Child(id, name, actions) { 
    Parent.apply(this, Array.from(arguments).slice(1))
    this.id = id; 
} 
const child1 = new Child(1, "c1", ["eat"]); 
const child2 = new Child(2, "c2", ["sing", "jump", "rap"]); 
console.log(child1); 
console.log(child2); 
console.log(child1.eat === child2.eat)

C. 组合继承:

概念:组合继承 = 原型链继承+盗用构造函数继承。

缺点:在使用子函数的实例对象时,其原型中会存在两份相同的属性/方法。(两份属性/方法的原因是:原型链继承的时候 new Parent 的过程里会有Parent.apply操作,盗用构造函数的时候也有Parent.apply操作,导致母函数里的属性和方法被同时放在子函数内和图中实例对象p上。)

原型是什么——阿尼亚知道🔥🔥🔥

D. 寄生组合继承:

概念:通过盗用构造函数继承属性 ,通过原型链混承来继承方法。

缺点:没有缺点,ES6的extends就是用寄生组合继承来实现的。

原型是什么——阿尼亚知道🔥🔥🔥

function Parent(name, actions) {
    this.name = name; 
    this.actions = actions; 
} 
Parent.prototype.eat = function () { console.log(`${this.name} - eat`); }; 
function Child(id) { 
    Parent.apply(this, Array.from(arguments).slice(1)); 
    this.id = id; 
} 
Child.prototype = Object.create(Parent.prototype); 
const child1 = new Child(1, "c1", ["sing"]); 
const child2 = new Child(2, "c2", ["jump"]); 

5、牛刀小试

下面这个图里有8个"原型家庭",来找找看。

原型是什么——阿尼亚知道🔥🔥🔥

6、结语

理解原型的关键在于理解构造函数原型对象实例对象三者的关系,可以将他们三者类比成一个家庭中的母亲父亲孩子。原型链相对于用__proto__将一个个的“原型家庭”链起来。

彩蛋阿廖沙的小屋 (alyosha.top)

转载自:https://juejin.cn/post/7194782845661872186
评论
请登录