11.1 原型
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象(函数.prototype)的用途是包含可以由特定类型的所有实例共享的属性和方法
11.2 原型链
基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法
原型链的作用:如果在对象上没有找到需要的属性或方法引用,引擎就会继续在原型关联的对象上寻找。
原型链就是多个对象通过
__proto__
的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数
Object
是所有对象的爸爸,所有对象都可以通过__proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过__proto__
找到它- 函数的
prototype
是一个对象 - 对象的
__proto__
属性指向原型,__proto__
将对象和原型连接起来组成了原型链
扩展
1. js中__proto__和prototype的区别和关系?
__proto__
是每个对象都有的一个属性,而prototype
是函数才会有的属性。__proto__
指向的是当前对象的原型对象,而prototype
指向的,是以当前函数作为构造函数构造出来的对象的原型对象。prototype
与__proto__
的关系就是:你的__proto__
来自你构造函数的prototype
11.3 原型继承和Class继承
首先先来讲下 class
,其实在 JS
中并不存在类,class
只是语法糖,本质还是函数
class Person {}
Person instanceof Function // true
1. 原型链继承
优点:实现函数复用,简单
缺点: 1.子类实例共享属性,造成实例间的属性会相互影响
2. 创建子类实例的时候,不能向超类型的构造函数中传递参数
function Super(){
this.color=['red','yellow','black']
}
function Sub(){
}
//继承了color属性 Sub.prototype.color=['red','yellow','black']
//原型链继承
Sub.prototype=new Super()
//创建实例 instance1.__proto__.color
const instance1=new Sub()
const instance2=new Sub()
console.log(instance1.__proto__.color===instance2.__proto__.color) //true
2. 借用构造函数继承
优点:可以在构造函数中向超类型构造函数传递参数
缺点:不能函数复用,方法只能定义在构造函数中
//构造函数继承
function Parent(name) {
this.name = name
this.rename = function () {
this.name.push('xiuzhu')
}
}
function Child() {
Parent.call(this, "chenyi")
}
var child1 = new Child()
var child2 = new Child()
console.log(child.name) //chenyi
console.log(child1.rename === child2.rename) // false, 实例没有共享同一个方法
3. 组合继承
组合继承是最常用的继承方式
function Parent(value) {
this.val = value
}
//原型式继承
Parent.prototype.getValue = function() {
console.log(this.val)
}
//构造函数继承
function Child(value) {
Parent.call(this, value)
}
//原型继承
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 继承方式优点:实现了函数复用,且能在构造函数中传参,不会与父类引用属性共享,可以复用父类的函数
- 缺点:会调用两次超类型的构造函数,一次在创建子类型原型的时候,另一次在子类型构造函数内部。就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
4. 原型式继承
- 引用类型值会共享,值类型不会共享
因为在改变值类型时,相当于给自己添加了属性。
当去修改引用类型的某个值时,是在修改__proto__中的对象。但如果直接给引用类型赋值,那也和值类型一样,是给自己增加了属性
function object(o){
function F(){}
//F.prototype={name:'ccdida',friends:['shelly','Bob']}
F.prototype=o
// new F()
//F是个构造函数,返回F的实例:1.this此时用不上 2.将实例的__proto__指向F.prototype.
//即返回了一个实例,其__proto__指向{name:'ccdida',friends:['shelly','Bob']}
return new F()
}
var person={
name:'ccdida',
friends:['shelly','Bob']
}
var person1=object(person)
var person2=object(person)
//object函数相当于实现了Object.Create的功能
console.log(person1.__proto__===person) //true
person2.friends.push('shlimy')
console.log(person1.friends)// ["shelly", "Bob", "shlimy"]
5. 寄生继承
缺点:不能做到函数复用,引用类型数据依然共享
原型式继承:基于已有的对象(原型对象)创建新对象(实现Object.create())
寄生式继承:创建一个用于封装继承过程的函数(实现Object.create()),同时以某种方式增强对象(比如添加方法)
var person={
name:'ccdida',
friends:['shelly','Bob']
}
function createAnother(original){
//clone.__proto__===original
var clone=Object.create(original)
//增强对象,添加属于自己的方法
clone.sayHi=function(){
console.log('hi')
}
return clone
}
var person1=createAnother(person)
var person2=createAnother(person)
person1.friends.push('shmily')
console.log(person2.friends)//["shelly", "Bob","shmily"]
person1.sayHi() //hi
4. 寄生组合继承
引用类型最理想的继承范式
前面的组合继承有个缺点:每次创建实例时都会调用两次超类方法,一次是通过
new
设置原型的时候,另一次是用call
执行的时候
function Parent(value) {
this.val = value
}
//原型式继承
Parent.prototype.getValue = function() {
console.log(this.val)
}
//构造函数继承
function Child(value) {
Parent.call(this, value)
}
//寄生继承
//Object.create()是ES5规范化的原型式继承:一个是用作新对象原型的对象和
//(可选的)一个新对象定义额外属性的对象
// 如下是补充因为重写Parent原型而失去默认的constructor属性,最后将新创建的对象赋值给子类型的原型
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 思路:不需要为了指定子类型的原型而调用超类型的构造函数(我理解为就是不需要显示的new操作),通过上面的寄生式继承方式来继承超类型的原型即可。
3. Class 继承
在 ES6 中,我们可以使用
class
去实现继承,并且实现起来很简单
class Parent {
constructor(value) {
this.val = value
}
getValue() {
console.log(this.val)
}
}
class Child extends Parent {
constructor(value) {
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
class
实现继承的核心在于使用extends
表明继承自哪个父类,并且在子类构造函数中必须调用super
,因为这段代码可以看成Parent.call(this, value)
。