对象冒充
对象冒充是通过利用构造函数使用this关键字给所有属性和方法赋值来实现继承,临时属性、call()、apply()、bind()都可以实现对象冒充。对象冒充可以支持多重继承,也就是说一个类可以继承多个类。
function Person(name) {
this.name = name;
this.say = function() {
console.log(this.name);
}
}
function F2E(name, id) {
// 临时属性方式
this.temp = Person;
this.temp(name);
this.temp = null;
// call()/apply()/bind()方式实质上是改变了this指针的指向
// Person.apply(this, [name]);
// Person.call(this, name);
// Person.bind(this)(name);
this.id = id;
this.showId = function() {
console.log(this.id);
}
}
var simon = new F2E("Simon", 9527);
simon.say();
simon.showId();
通过对象冒充方式继承时,所有的成员方法都是指向this的,也就是说new之后,每个实例将都会拥有这个成员方法,并不是共用的,这会造成大量的内存浪费。并且通过对象冒充的方式,无法继承通过prototype方式定义的变量和方法。
原型链方式
JavaScript中每个对象都有一个隐藏的__proto__属性,一个实例化对象的__proto__属性指向其类的prototype方法,而这个prototype方法又可以被赋值成另一个实例化对象,这个对象的__proto__又需要指向其类,由此形成一条链,这就是原型链。
原型链继承是JavaScript中实现继承最简单的方法,只需要将子类型的原型指向父类型的实例就行了。
JavaScript对象在读取某个属性时,会先查找自身属性,没有则再去依次查找原型链上对象的属性。也就是说原型链的方法是可以共用的,这样就解决了对象冒充浪费内存的缺点。
function Person(name) {
this.name = name;
this.say = function() {
console.log(this.name);
}
}
function F2E(name, id) {
this.id = id;
this.showId = function() {
console.log(this.id);
}
}
F2E.prototype = new Person();
var simon = new F2E("Simon", 9527);
simon.say(); // undefined
simon.showId(); // 9527
通过原型链实现继承在实例化子类时不能将参数传给父类,因此将两种继承方式结合起来使用会更好。
function Person(name) {
this.name = name;
this.say = function() {
console.log(this.name);
}
}
function F2E(name, id) {
// 成员变量采用对象冒充方式
this.temp = Person;
this.temp(name);
this.temp = null;
// Person.apply(this, [name]);
// Person.call(this, name);
// Person.bind(this)(name);
this.id = id;
this.showId = function() {
console.log(this.id);
}
}
F2E.prototype = new Person(); // 成员方法采用原型链方式
F2E.prototype.constructor = F2E; // 替换prototype对象之后需要把constructor属性指回原来的构造函数
var simon = new F2E("Simon", 9527);
simon.say();
simon.showId();
ES6继承(class, extends, super)
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。
class Animal {
// 构造方法
constructor(){
// this关键字代表实例对象
this.type = "animal";
}
// constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的
says(say){
console.log(this.type + " says " + say)
}
}
// Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
class Cat extends Animal {
constructor(){
// super关键字指代父类的实例(即父类的this对象)
super(); // 子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。所以子类必须在constructor方法中调用super方法,否则将获取不到this对象。
this.type = "cat";
}
}
let cat = new Cat();
cat.says("hello"); //cat says hello
ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。