构造与原型链
JavaScript中类的创建与其他语言(如Java)类似。
- 使用
constructor()
来创建类的构造函数。 - 类中的方法不需要添加
function
关键字。 - 使用
extends
关键字实现继承,例如:
class Son extends Father {
constructor(surname, firstname) {
super(surname);
this.firstname = firstname;
}
super.say();
}
- 使用
super
来调用父类的函数,可以是构造函数,也可以是普通函数。但要注意,如若在constructor()
中使用super
,必须放在constructor的第一行,也就是所有this
语句的前面。使用super
调用普通函数时,按照super.func()
的格式调用。 - ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
- 类的构造函数存在内存浪费的问题:
主要是存在为方法重复开辟内存空间里的。 - 构造函数通过原型分配的函数是所有对象所共享的。JavaScript 规定,每一个构造函数都有一个
prototype
属性,指向另一个对象。注意这个prototype
就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。我们可以把那些不变的方法,直接定义在prototype
对象上,这样所有对象的实例就可以共享这些方法。 - 原型的作用就是共享方法。
- 原型的使用:
class Son extends Father {
constructor(surname, firstname) {
super(surname);
this.firstname = firstname;
}
Son.prototype.say = function () {
console.log('say something');
}
}
- 一般情况下,公共属性放在构造函数中,公共方法放在原型对象里。
- 对象身上系统会自动添加
__proto__
指向构造函数的原型对象。 prototype
是原型对象,__proto__
是对象的原型,它们都有一个名为constructor
的属性指向构造函数。- 当
prototype
中需要添加较多的方法时,可以采用下面的方式:
Star.prototype = {
constructor: Star,
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
这里需要注意的是第一行,如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor
指回原来的构造函数。
-
构造函数、实例与原型对象的关系
prototype
对象是构造函数构造出的对象的父级 -
只要是对象,就有
__proto__
原型,指向原型对象。 -
原型链
-
成员的查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是
__proto__
指向的prototype
原型对象)。 - 如果还没有就查找原型对象的原型(
Object
的原型对象)。 - 依此类推一直找到
Object
为止(null
)。 __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
-
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
继承
- ES6之前并没有给我们提供
extends
继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承
。 - 组合继承使用
call
关键字实现,其格式为:
fun.call(thisArg, arg1, arg2, ...)
通常,thisArg
为子类this
指针,fun
为父类,arg
为各参数变量。
实例:
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
- 一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。借用原型对象继承父类型方法的核心原理如下:
- 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
- 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
- 将子类的
constructor
从新指向子类的构造函数
实例:
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
类的本质
class
的本质是function
.- 类的所有方法都定义在类的
prototype
属性上。 - 类创建的实例,里面也有
__proto__
指向类的prototype
原型对象。 - ES6的类的本质是
语法糖
。