-
1. 构造函数
1.1 什么是构造函数
首先构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数习惯首字母大写。而构造函数与普通函数的区别在于调用方式不同,构造函数需要用new关键字来调用。
1.2 为什么要用构造函数
抽取对象公用的属性和方法封装成一个类(也就是一个模板),我们可以通过对类进行实例化,这些实例化对象就可以具有相似的属性和方法,从而减少冗余代码,实现 代码复用。
function Star(name, skill) {
this.name = name;
this.skill = skill;
}
var star1 = new Star('刘德华', '唱歌')
var star2 = new Star('杨紫', '表演')
console.log(star1) // Star {name: "刘德华", skill: "唱歌"}
console.log(star2) //Star {name: "杨紫", skill: "表演"}
1.3 构造函数的执行过程
构造函数是用new关键字进行调用的,所以构造函数的执行过程也就是new的执行过程,主要有以下4步:
1. 创建一个空对象;
2. 设置原型链:将这个对象的__proto__指向构造函数的prototype;
3. 将新创建的对象作为this的上下文;
4. 如果这个函数有返回值, 则返回; 否则默认返回新对象。
-
2. 原型
只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性,指向原型对象。原型对象也有一个constructor属性,指回与之关联的构造函数。每次调用构造函数创建一个实例,这个实例就会有一个__proto__的属性,指向原型对象。
三者关系如图:
- 判断
instanceof检测构造函数的prototype属性是否出现在某个实例对象的原型链上,返回布尔值
function Star(name, skill) {
this.name = name;
this.skill = skill;
}
var star1 = new Star('刘德华', '唱歌')
console.log(star1 instanceof Star) //true
isPrototypeOf( )原型对象的方法,检查实例中是否有__proto__指向obj.prototype,MDN文档解释:测试一个对象是否存在于另一个对象的原型链上
function Star(name, skill) {
this.name = name;
this.skill = skill;
}
var star1 = new Star('刘德华', '唱歌')
console.log(Star.prototype.isPrototypeOf(star1)) //true
- 获取
Object.getPrototypeOf(object)获取指定对象的原型 -
3. 原型链
通过对象访问属性或方法时,会先搜索实例本身;若没有,则通过__proto__指向的原型对象中查找属性或方法,如果还没有找到就会继续沿__proto__寻找,直至Object的原型对象,这样一层一层向上查找就会形成一个链式结构,成为原型链。
原型链的终点:Object的原型对象也有__proto__,指向null,即Object.prototype.__proto__ === null
注:给对象的实例增加一个同名属性,会遮蔽原型对象的同名属性,虽然不会修改它,但是会屏蔽对它的访问
- 判断
hasOwnPropertypeOf( )确定某个属性在实例还是在原型对象上。存在它的实例上时返回truein无论改属性在实例还是原型上都返回true
- 枚举
for…in通过对象访问且可被枚举的属性都会返回,包括实例属性和原型属性,枚举顺序不确定Object.keys()返回对象实例上所有可枚举的属性名称的字符串,枚举顺序不确定Object.getOwnPropertyNames()列出所有实例属性,无论是否可被枚举(包括constructor),枚举顺序确定
-
4.继承
4.1 原型链继承
创建父亲的实例,并将该实例赋值给子.prototype
function Father(name) {
this.name = name
this.color = ['red','black','pink']
}
Father.prototype.getName = () => {
console.log('我是父亲的方法')
}
function Son(score) {
this.score = score
}
Son.prototype = new Father('张三') //核心代码
var son1 = new Son(34)
son1.getName()
console.log(son1.name, son1.color)
输出结果:
- 缺点: (1)多个实例对引用类型的操作会被篡改
var son2 = new Son(99)
son1.color.push('gary')
console.log(son2.name)
console.log(son1.color)
console.log(son2.color)
(2)在创建子类实例时,不能向父类传参
4.2 借用构造函数
使用父类的构造函数增强子类的实例,即复制父类的实例给子类
function Father(name) {
this.name = name
this.color = ['red','black','pink']
this.getName = function () {
console.log('我是父类构造函数的方法')
}
}
function Son(name) {
Father.call(this, name) //核心代码 在新创建的对象上执行构造函数
}
var stu1 = new Son('李四')
var stu2 = new Son('王五')
console.log(stu1.name)
console.log(stu1.color)
console.log(stu2.name)
console.log(stu2.color)
stu1.getName()
- 优点 (1)避免了引用类型的属性被所有实例共享
var stu1 = new Son('李四')
var stu2 = new Son('王五')
stu2.color.push('gray')
console.log(stu1.name)
console.log(stu1.color)
console.log(stu2.name)
console.log(stu2.color)
(2)可在子类中向父类传参
(3)可以实现多继承(call多个父类对象)
- 缺点
(1)只能继承父类实例的属性和方法,不能继承原型的
(2)实例并不是父类的实例,而是子类的实例
(3)无法实现函数的复用,每个子类都有父类实例函数的副本,影响性能
4.3 组合继承
上述两种方式的结合,通过原型链实现对原型属性或方法的继承,构造函数实现实例的属性或方法的继承
function Father(name) {
this.name = name
this.color = ['red','black','pink']
}
Father.prototype.getName = () => {
console.log('我是父亲的方法')
}
function Son(name) {
Father.call(this, name) //第二次调用,从父类拷贝一份实例属性给子类
}
Son.prototype = new Father() //第一次调用,创建父类的实例作为子类的原型
var stu1 = new Son('张三')
var stu2 = new Son('李四')
stu2.color.push('yellow')
console.log(stu1.name)
console.log(stu1.color)
stu1.getName()
console.log(stu2.name)
console.log(stu2.color)
- 优点
(1)不存在引用属性共享的问题
(2)可以传递参数
(3)函数可以复用 - 缺点 使用子类创建实例对象时,父类被调用了两次,子类原型中会存在两份相同的属性和方法
4.4 原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型
function Object(obj) {
function F() {}
F.prototype = obj
return new F()
}
function Father(name) {
this.name = name
this.color = ['red','black','pink']
}
Father.prototype.getName = () => {
console.log('我是父亲的方法')
}
var father = new Father('李四')
var stu1 = Object(father)
var stu2 = Object(father)
stu2.color.push('yellow')
console.log(stu1)
console.log(stu1.name)
console.log(stu1.color)
console.log(stu2.color)
注:ES5中的Object.create()能够代替上述的Object方法
var stu1 = Object.create(father)
- 缺点 与原型链继承相同
4.5 寄生式继承
在原型式继承的基础上,增强对象,返回构造函数
function Object(obj) {
function F() {}
F.prototype = obj
return new F()
}
function Father(name) {
this.name = name
this.color = ['red','black','pink']
}
Father.prototype.getName = () => {
console.log('我是父亲的方法')
}
function creatAnother(original) {
var clone = Object(original)
clone.sayHi = function () {
console.log('hi')
}
return clone
}
var stu1 = creatAnother(new Father('王五'))
var stu2 = creatAnother(new Father('李四'))
stu2.color.push('yellow')
console.log(stu1)
console.log(stu1.name)
console.log(stu1.color)
stu1.sayHi()
stu1.getName()
console.log(stu2.name)
console.log(stu2.color)
- 缺点
(1)无法实现函数的复用
(2)无法传参
4.6 寄生组合式
结合借用构造函数传递参数和寄生模式,即 取得父类原型的副本,使用寄生式来继承父类的原型,然后将返回的新对象赋值给子类的原型
function Father(name) {
this.name = name;
this.color = ['red','black','pink']
}
Father.prototype.getName = function () {
console.log('我是父类的方法')
}
function Son(name, age) {
Father.call(this, name) //核心代码 借用父类构造函数增强子类实例
this.age = age
}
let prototype = Object.create(Father.prototype) //取得父类原型副本
prototype.constructor = Son //重写constructor,增强对象
Son.prototype = prototype //返回父类原型对象副本赋值给子类的原型
Son.prototype.sayHi = function () {
console.log('hi')
}
let stu1 = new Son('张三',18)
console.log(stu1)
console.log(stu1.name, stu1.age)
console.log(stu1.color)
stu1.getName()
stu1.sayHi()
- 优点 是目前效率最高的继承方式,既只调用了一次父类构造函数,同时还能保持原型链不变