原型是什么——阿尼亚知道🔥🔥🔥
面试官:介绍一下原型和原型链
我:(这题我背过)在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__将一个个的“原型家庭”链起来。
转载自:https://juejin.cn/post/7194782845661872186