一、理解一些基本概念
(1)理清构造函数、对象实例、原型对象prototype和对象原型__proto__的关
- 构造函数:用于建立对象的函数,经过new关键字生成对象实例。函数名通常首字母大写的。
- 对象实例:new关键字生成对象实例。
- 原型对象prototype:每一个函数都有一个prototype属性,它是一个指向原型对象的指针(原型对象在定义函数时同时被建立)。
- 对象原型:构造函数创建的每个实例对象都有一个__proto__属性,用来指向构造函数的原型对象prototype。可以理解为两者是等价的。
(2)原始值、引用值、值传递
- 原始值:表示单一的数据,保存原始值的变量是按值访问,操作存储在变量内存中的实际值。ES6有undefined,Null,Boolean,Number,String,Symbol等六种原始值
- 引用值:引用值是指由多个值构成的对象。实际操作对象时,访问的是保存对象的内存地址,即该对象的引用。引用值(对象)可以随时添加,修改和删除其属性的方法(动态属性)。
- 值传递:即将值复制给变量的过程。与原始值一样都是将变量中保存的信息赋值给另一个变量。
二、继承的六种方式
1.原型链继承(prototype继承)
function Person() {
this.color = ['red', 'green']
}
Person.prototype.getColor = function() {
return this.color
}
function My(name) {
this.name = name
}
// 通过创建My的实例继承了Person
My.prototype = new Person()
var m1 = new My('ljc')
var m2 = new My('xlt')
m1.color.push('black')
console.log(m1.color, m2.color)
小结:原型对象的所有属性(引用值)会被所有实例共享,这会导致对⼀个实例的修改会影响另⼀个实例。
2. 构造函数继承(借助 call或apply)
function Person() {
this.color = ['red', 'green']
}
Person.prototype.getColor = function() {
return this.color
}
function My(name) {
Person.call(this)
this.name = name
}
var m1 = new My('ljc')
var m2 = new My('ljc')
m1.color.push('black')
console.log(m1.color)
console.log(m2.color)
console.log(m1.getColor())
call和apply传参的区别,前者是列表形式,而后者是以数组形式
小结:解决了原型链继承的缺点(原型对象的所有属性((引用值))共享),不能继承原型(prototype)属性或者方法。
3. 组合继承(结合原型链继承+构造函数继承)
function Person(name) {
this.name = name
this.color = ['red', 'green']
}
Person.prototype.getColor = function() {
return this.color
}
function My(name) {
Person.call(this, name)
// Person.apply(this, [name])
}
My.prototype = new Person()
// 手动挂上构造器,指向自己的构造函数
My.prototype.constructor = My
var m1 = new My('ljc')
var m2 = new My('xlt')
m1.color.push('black')
console.log(m1.color)
console.log(m2.color)
console.log(m1.getColor())
console.log(m2.getColor())
小结:解决了前两种的缺点,但无论在什么情况下,都会调用两次父类(Person)的构造函数,一次是在创建子类型原型的时候(My.prototype = new Person()),一次是在子类型构造函数的内部(Person.call(this, name)),存在效率问题。
4. 原型式继承
ES5之后使用Object.create()方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
const obj = {
color: ['red', 'green', 'blue'],
name: 'obj'
}
const t1 = Object.create(obj)
const t2 = Object.create(obj)
t1.color.push('balck')
t1.name = 'ljc'
console.log(t1.color, t2.color)
console.log(t1.name, t2.name)
小结:对一个对象进行浅克隆创建另一个对象,同时继承该对象的原型属性,于是浅克隆,所以实例共享的对象属性如果是引用值,会受污染。
5. 寄生式继承
const obj = {
color: ['red', 'green', 'blue'],
name: 'obj',
getName: function() {
return this.name
}
}
function clone(obj) {
const newObj = Object.create(obj)
newObj.getColor = function() {
return this.color
}
return newObj
}
const t1 = clone(obj)
const t2 = clone(obj)
t1.color.push('balck')
t1.name = 'ljc'
console.log(t1.color, t2.color)
console.log(t1.name, t2.name)
console.log(t1.getName(), t2.getName())
console.log(t1.getColor(), t2.getColor())
小结:根据一个对象克隆创建另一个对象,并增强对象。通过寄生式继承给对象添加函数会导致函数难以复用,与构造函数模式类似。
6. 寄生式组合继承(构造函数+原型式继承)
function Person(name) {
this.name = name
this.color = ['green', 'red']
}
Person.prototype.getName = function() {
return this.name
}
function My(name, age) {
Person.call(this, name)
this.age = age
}
function clone(Sup, Sub) {
Sub.prototype = Object.create(Sup.prototype)
Sub.prototype.constructor = Sub
}
clone(Person, My)
My.prototype.getAge = function() {
return this.age
}
const m1 = new My('ljc', 24)
const m2 = new My('xlt', 23)
m1.color.push('black')
m2.color.push('white')
console.log(m1.color, m2.color)
console.log(m1.getName(), m1.getAge())
小结:基本可以解决前几种继承方式的缺点,只调用了一次父类的构造函数(Person.call(this, name)),提高效率。
三、ES6的extends关键字实现逻辑
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class My extends Person {
constructor(name, age) {
super(name)
this.age = age
}
}
const my = new My('ljc', 23)
console.log(my.getName(), my.age)