1、js中的原型和原型链
和其他的面向对象编程语言不同,最开始js并没有引入class的概念,但是js中有在大量使用对象,为了保证对象之间的联系,JavaScript引入了原型与原型链的概念。
1.1、什么是原型
在js中,每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。原型对象默认拥有一个constructor属性,指向指向它的那个构造函数,每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象。原型对象就是用来存放实例中共有的那部分属性。
1.2. 什么是原型链
js中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链, 所有原型链的终点都是Object函数的prototype属性.
1.3. 常见的题目
Array.__proto__ === Function.prototype // true
Array.__proto__.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
function F(){}
Function.prototype.a = function(){
console.log('a1')
}
Function.prototype.b = function(){
console.log('b1')
}
Object.prototype.a = function(){
console.log('a2')
}
Object.prototype.b = function(){
console.log('b2')
}
let f = new F()
f.a()
f.b()
F.a()
F.b()
2、js中的继承
js主要存在6种继承方式。
2.1 原型链继承
function Parent(){
this.parent = [1,2,3]
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(child){
this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
优点:
- 这种方式的优点很明显,多个实例可以共享原型链上定义的属性和方法。
缺点:
- 每个实例对引用类型的属性的修改也会被其他实例共享,这不是我们想看到的
- 创建child的时候无法像构造函数传参,child实例无法初始化父类属性
2.1 构造函数继承
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
优点:
- 克服了原型链继承带来的2个缺点
缺点:
- 子类无法继承父类原型链上的方法;
- 每次生成子类实例都会执行一次父函数
2.2 组合继承(常用的一种方式)
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
优点:解决了上面两种方法的缺点
- 子类的构造函数会代替原型上的那个父类构造函数
- 每个新实例引入的构造函数属性是私有的
缺点:
- 父类的构造函数执行两次Parent.call(this,name)/new Parent()
- 子类的构造函数会代替原型上的那个父类构造函数
2.4 寄生组合继承(常用)
function Parent(parent){
this.parent = parent
}
Parent.prototype.getName = function(){
console.log(this.parent)
}
function Child(name,child){
Parent.call(this,name)
this.child = child
}
function inheritPrototype(Parent, Child){
//创建父类原型的一个副本,把副本赋值给子类原型,而不是new Parent(),减少一次父构造函数的调用
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
}
解决了组合继承中父类构造函数执行两次的问题
继承扩展的知识点
1. 是Object.create的原理
Object.create => function(obj){
var f = function(){};
f.prototype = obj;
return new f();
}
先在内部创建一个空构造函数
把构造函数的原型指向传进来的obj对象
通过new创建对象并返回
2. Object.create,new Object(), {}三个的区别(百度)
Object.create是使用指定的原型proto对象及其属性properties去创建一个新的对象,也就是我们新建的对象的原型对象是传入create的对象。
值得注意的是当create的参数为null的时候创建的新对象完全是一个空对象,没有原型,也就是没有继承Object.prototype上的方法。
new Object()和{}本质上没有区别,它们创建的新对象的原型指向的是 Object.prototype。
new Object()和{}在初始化的过程上有区别,前者是用构造函数实例化对象,后者是直接创建JSON对象,后者的初始化比较方便,可以在初始化的时候同时赋值。而两种方法创建的对象在使用上都是一样的,所以使用的时候都建议用后者,没有new的过程比较高效。
3. new的原理
function myNew () {
//创建一个实例对象
let obj = new Object()
//传入构造函数
let fn = Array.prototype.shift.call(arguments)
let args = Array.prototype.slice.call(arguments,1)
//实现继承
obj.__proto__ = fn.prototype
//调用构造器,改变this指向到实例
let result = fn.apply(obj,args)
return typeof result === 'object' ? result : obj
}
- 创建一个新对象
- 将构造函数的作用域赋给新对象
- 调用构造器,改变this指向到实例,执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象。