文章目录
(读书笔记),详见红皮书162-174
一、类的声明和实例生成
类声明的方式:
构造函数方法、es6的class关键字
//第一种方式 构造函数
function People(name) {
this.name = name;
}
var xiaoming = new People("xiaoming")
console.log(xiaoming); // {name: "xiaoming"}
// 第二种方式:es6中class关键字
class Person {
constructor(name) {
this.name = name;
}
}
var xiaohong = new Person("xiaohong");
console.log(xiaohong); // {name: "xiaohong"}
确定原型和实例之间关系的方法:
var Tom = new Son();
console.log(Tom instanceof Son) // true
console.log(Son.prototype.isPrototypeOf(Tom)) // true
第一种:instanceof
第二种:isPrototypeOf()
二、继承
继承的几种方式:
- 原型链
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承(最理想)
- es6中extends关键字
1、原型链 (很少单独使用)
//定义父类,即超类型构造函数
function Father() {
this.gender = "man";
}
// 给父类原型上添加方法
Father.prototype.Say = function() {
return "Today is a nice day";
}
// 定义子类,即子类型构造函数
function Son() {
this.name = "Tom";
}
// 继承父类:让子类的原型等于父类的实例
Son.prototype = new Father();
//给子类原型上添加方法
//(子类原型上添加方法或重写父类型中的方法一定要放在替换原型的语句之后)
Son.prototype.Hello = function() {
return "Hello everyone, my name is " + this.name;
}
// 让子类生成实例
var Tom = new Son();
console.log(Tom.Hello()); // Hello everyone, my name is Tom
console.log(Tom.gender); // man
console.log(Tom.Say()); // Today is a nice day
console.log(Tom instanceof Son); // true
console.log(Tom instanceof Father); // true
console.log(Tom.constructor); // Father *********特别注意此处
console.log(Tom.__proto__ === Son.prototype); // true
console.log(Tom.__proto__.__proto__ === Father.prototype); // true
思路 :利用原型链中构造函数、原型、实例关系。
实现:通过Son.prototype = new Father(),子类既可以继承父类的属性和方法,又可以添加新方法。
注意:
1、子类添加方法或重写父类型中的方法一定要放在替换原型的语句之后;
2、通过原型链实现继承时,不能使用对象字面量创建原型方法。
问题:
1、子类的实例Tom的constructor指向了父类Father;
2、包含引用类型值的原型属性会被所有实例共享;
3、在创建子类型的实例时,不能向父类的构造函数中传递参数。
2、借用构造函数 (很少单独使用)
为了解决原型中包含引用类型值所带来问题
//定义父类
function Father(age) {
this.roomContain = ["sofa", "table", "bed"];
this.age = age;
}
// 定义子类
function Son() {
Father.call(this, "16"); // 继承了Father*************************重要
}
var Tom = new Son();
Tom.roomContain.push("TV");
console.log(Tom.roomContain); // ["sofa", "table", "bed", "TV"]
var Jack = new Son();
console.log(Jack.roomContain); // ["sofa", "table", "bed"]
console.log(Jack.age); // 16
思路 :在子类型构造函数的内部调用超类型构造函数(父类构造函数)。
实现:通过Father.call(this),解决了原型上共用引用类型属性的问题。
注意:为了确保超类型构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中
定义的属性。
问题:方法都在构造函数中定义,函数不能实现复用,子类不能访问父类原型中定义的方法。
3、组合继承 (js中最常用继承模式)
将原型链和借用构造函数的技术结合
//定义父类
function Father(age) {
this.roomContain = ["sofa", "table", "bed"];
this.age = age;
}
Father.prototype.Say = function() {
return "Today is a nice day";
}
// 定义子类
function Son(age, name) {
Father.call(this, age); // 继承了Father*********************重要*******************第二次调用
this.name = name;
}
// 继承方法
Son.prototype = new Father(); // ***********************重要*************第一次调用
Son.prototype.constructor = Son; // **********************重要
Son.prototype.Hello = function() {
return "Hello everyone, my name is " + this.name;
}
var Tom = new Son("16", "Tom");
Tom.roomContain.push("TV");
console.log(Tom.roomContain); // ["sofa", "table", "bed", "TV"]
console.log(Tom.Say()); // Today is a nice day
console.log(Tom.Hello()); // Hello everyone, my name is Tom
console.log(Tom.age); // 16
var Jack = new Son("20", "Jack");
console.log(Jack.roomContain); // ["sofa", "table", "bed"]
console.log(Jack.Say()); // Today is a nice day
console.log(Jack.Hello()); // Hello everyone, my name is Jack
console.log(Jack.age); // 20
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
实现:让子类的实例既分别拥有自己属性,又可以使用相同方法。
优点:组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且, instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。
缺点:会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
第一次调用Father父类构造函数时,Son.prototype会得到两个属性(roomContain、age),它们都是Father的实例属性,现在位于Son的原型中;
第二次调用在子类的新对象上创建了实例属性(roomContain、age),这两个属性屏蔽了原型中同名的两个属性。
总结为有两组属性,一组在子类实例上,一组在子类原型上
解决方法:Son.prototype = new Father();修改为:Son.prototype = Object.create(Father.prototype)(ES5新增)
这样Tom的原型上就没有多余的属性了
4、原型式继承
function object(o) {
function F() { } //函数内创建一个临时性构造函数
F.prototype = o; // 将传入的对象作为构造函数的原型
return new F(); // 返回构造函数的实例
}
// 作为基础的对象
var person = {
name: "Mary",
roomContain: ["sofa", "table", "bed"]
};
var person1 = object(person);
console.log(person1); // {}, 此对象的__proto__指向F.prototype,因此指向参数o,此处为person1
person1.name = "Tom";
person1.roomContain.push("TV");
console.log(person1); // {name: "Tom"}
console.log(person1.roomContain); // ["sofa", "table", "bed", "TV"] 为原型上的属性********输出见下图*****
var person2 = object(person);
person2.name = "Jack";
person2.roomContain.push("books");
console.log(person2); // {name: "Jack"}
console.log(person2.roomContain); // ["sofa", "table", "bed", "TV", "books"]
console.log(person.roomContain); // ["sofa", "table", "bed", "TV", "books"]
console.log(person1.roomContain); // ["sofa", "table", "bed", "TV", "books"]*******还是共享了属性*******
思路:借助原型可以基于已有的对象创建新对象。
要求:必须有一个对象(person)可以作为另一个对象的基础,传递给 object() 函数,返回一个新对象,新对象将传入的参数person作为原型,因此原型person上的属性和方法为创造的对象所共享。
问题:包含引用类型值的属性始终都会共享相应的值。
拓展:ES5通过**Object.create()** 方法规范化了原型式继承。参数有两个:第一个作为新对象的原型对象,第二个(可选)新对象定义额外属性的对象,指定的任何属性都会覆盖原型对象上的同名属性。传入一个参数时与object方法行为相同。
5、寄生式继承
// 接收一个参数original,作为新对象的基础对象
function introPerson(original) {
var clone = Object(original); //创建一个新对象,只要能返回新对象函数都可以,可以使用上方法中自定义object函数
clone.sayHi = function() {
console.log("Hi");
};
return clone; //返回对象
}
var person = {
name: "Mary",
roomContain: ["sofa", "table", "bed"]
};
var person1 = introPerson(person); //新对象person1,不仅具有person所有属性和方法,还有自己的sayHi方法
person1.sayHi(); // Hi
console.log(person1.name, person1.roomContain); // Mary ["sofa", "table", "bed"]
console.log(person1); // {name: "Mary", roomContain: ["sofa", "table", "bed"], sayHi: ƒ}
问题:函数复用效率低,跟上一个方法相比并没有太大改善
6、寄生组合式继承 (引用类型最理想的继承)
//定义函数,接收两个参数:子类构造函数,父类构造函数
function realizeExtends(Son, Father) {
var o = Object(Father.prototype);
o.constructor = Son;
Son.prototype = o;
}
//定义父类
function Father(age) {
this.roomContain = ["sofa", "table", "bed"];
this.age = age;
}
Father.prototype.Say = function() {
return "Today is a nice day";
}
// 定义子类
function Son(age, name) {
Father.call(this, age); // 继承了Father
this.name = name;
}
// 继承方法
realizeExtends(Son, Father)
Son.prototype.Hello = function() {
return "Hello everyone, my name is " + this.name;
}
var Tom = new Son("16", "Tom");
console.log(Tom); //{roomContain: Array(3), age: "16", name: "Tom"}
console.log(Son.prototype); // {Say: ƒ, Hello: ƒ, constructor: ƒ}
优点:相比于组合式继承而言,只调用了一次父类构造函数,避免了在子类原型上创建不必要、多余的属性,具有高效率。原型链能保持不变,能够正常使用instanceof 和 isPrototypeOf()。
7、es6中extends关键字
// 定义父类
class People {
// 构造函数定义属性
constructor(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 普通方法————给实例调用的
sayHello() {
console.log("Hello, my name is " + this.name);
}
// 静态方法————给类调用的
static hello() {
console.log("hello")
}
}
// 定义子类
class Student extends People {
// 构造函数定义属性
constructor(name, age, sex, grade) {
// 前三个属性可以继承 最后一个不可以
super(name, age, sex);
// 赋值自己的属性
this.grade = grade;
}
}
// 实例化一个父类
let p = new People("张三", 12, "男");
// 调用方法
p.sayHello(); // Hello, my name is 张三
// 实例化一个子类
let s = new Student("小明", 13, "男", 6);
// 调用方法
s.sayHello(); // Hello, my name is 小明
People.hello(); // hello