JS继承
对象的创建和JS继承 知识点是类似的 放在一起好记
前置知识:无论理解继承还是js对象的创建都需要先理解原型和原型链的知识
继承有:1.原型链继承 2.构造函数继承 3.组合继承 4.原型式继承 5.寄生式继承 6.组合寄生继承 7.extends关键字
1.原型链实现继承
原型链实现继承的关键一句是Cat.prototype = new Animal();
// 父类构造函数
function Animal() {
this.property = "略略略";
}
// 获取父类的属性
Animal.prototype.getAnimalValue = function () {
return this.property;
};
// 子类构造函数
function Cat() {
this.voice = "喵喵喵";
}
// 继承了Animal
Cat.prototype = new Animal();
// 获取子类的属性
Cat.prototype.getCatValue = function () {
return this.voice;
};
// 实例化对象
var mimi = new Cat();
alert(mimi.getAnimalValue()); //"略略略"
Cat.prototype = new Animal()转成图片理解如下图![]()
原型链继承的缺点:
- 原型上的属性,特别是包含
引用类型值的需要注意,因为原型的属性方法会被所有实例共享 - 创造
子类型的实例时,不能向超类型的构造函数中传递参数
2.借用构造函数实现继承
借用call或则apply来调用父类构造函数,给子类this上挂属性
function Animal(voice){
this.voice = voice || "略略略";
}
Animal.prototype.say = function(){
console.log(this.voice);
}
function Cat(){
Animal.call(this, "喵喵喵")
}
const mini = new Cat();
alert(mini.voice) // "喵喵喵"
这种方式解决了原型链方式不能给超类型的构造函数中传递参数,以及原型对象上的属性共享问题,但是缺点也很明显,挂载超类原型上的方法,子类是拿不到的
3.组合继承(原型链+构造函数)
原型链和构造函数果然是好兄弟
function Animal(voice){
this.voice = voice || "略略略";
}
Animal.prototype.say = function(){
console.log(this.voice);
}
function Cat(){
Animal.call(this, "喵喵喵")
}
// 继承Animal
Cat.prototype = new Animal();
const mini = new Cat();
console.log(mini.voice) // "喵喵喵"
mini.say() // "喵喵喵"
Animal.call和Cat.prototype = new Animal()相结合,既继承了超类型的属性也继承了超类型原型上的方法
缺点是要调用两次构造函数
4. 原型式继承
原型式继承核心如下
function object(o){
function F(){}
F.prototype = o;
return new F();
}
ES5新增的Object.create只传一个参数时可以平替object方法
const cat = {
voice: "喵喵喵",
toys: ["皮球","毛球"]
}
const mimi = Object.create(cat) // 或则const mimi = object(cat)
mimi.toys.push("逗猫棒")
const maomao = Object.create(cat)
maomao.toys.push("老鼠")
console.log(mimi.toys); // ['皮球', '毛球', '逗猫棒', '老鼠']
console.log(cat.toys) // ['皮球', '毛球', '逗猫棒', '老鼠']
object方法中的F.prototype = o与原型链继承中的Cat.prototype = new Animal()本质相同,new出来的对象也是个对象,实例化之后示例就会继承其原型对象身上的方法属性。
缺点和原型链继承相同
Object.create还可以帮助实现类式继承
5. 寄生式继承
寄生式继承在原型式继承上封装了一个方法(工厂函数)返回一个对象
function createAnother(original){
var clone = object(original);//创建一个新对象
// 在这里可以对对象做增强,比如添加方法
return clone;
}
const mimi = createAnother(cat)
寄生式继承也有原型式继承的缺点
6. 寄生组合式继承
function Animal(params) {
this.toys = ["毛球","皮球"]
}
Animal.prototype.say = function () {
console.log(this.voice);
}
function object(obj) {
const F = function () {}
F.prototype = obj
return new F()
}
// 本质也可以理解为一个工厂函数 做了该做的事
function inherit(father,child) {
const prototype = object(father)
prototype.constructor = child // 改变原型对象的constructor的指向
child.prototype = prototype // 修复实例
}
function Cat() {
Animal.call(this) // 继承属性
this.toys.push("逗猫棒")
this.voice = "喵喵"
}
inherit(Animal.prototype,Cat) // 用来继承方法
const maomao = new Cat()
const dog = new Animal() // 打印toys看看
inherit主要做了什么呢? 看图解:
object+inherit的方式不调用超类型构造函数,解决了第三种继承方式调用两次构造函数造成的浪费,核心是借用F一个空的构造函数来作为桥梁,搭建子类到超类的原型链路。
7.es6 extends关键字 super关键字 类继承
其中TS中extends关键字也可以用来实现interface的继承
extends关键字用法很简单,但是要配合super关键字使用:
1. super作为父类构造函数在子类constructor中调用
class Animal{
constructor (){
this.name = "animal" // 给Animal实例对象上加属性
}
say(){ // 相当于给Animal.prototype加方法
console.log(this.name);
}
}
class Cat extends Animal {
constructor(){ // constructor可以不写,默认会有并且会帮调用super
super() // 这里super代表的是父类构造函数
this.name = "cat"
}
}
const cat = new Cat()
cat.say() // cat
这里super代表的是父类构造函数,有且只能在子类构造器中调用super方法,不调用js会报错:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
与上面组合继承中的调用父类构造函数不同,类继承中子类中调用super虽然执行的是父类的constructor,但是this指向子类,返回的是子类的实例,有点类似在11行执行了Animal.prototype.constructor.call(this)
2. 在普通方法或则子类constructor中 super作为对象调用
class Cat extends Animal {
constructor(){
super()
this.name = "cat"
}
watch(){
super.say();// cat
console.log(super.name);// undefined
}
}
const cat = new Cat()
cat.watch()
为什么拿不到super.name呢?
是因为super作为对象调用时相当于Animal.prototype,属性name挂在Animal实例对象上,方法say挂载Animal原型对象上。
在watch方法中通过super调用的say方法中的this指向子类实例。类似于Animal.prototype.say.call(this)
注意:如果通过super给属性赋值,此时super就等同于this
watch(){
super.name = "mimi" // 等于this.name = "mimi"
super.say() // mimi
console.log(super.name); // undefined
}
3. 在static关键字方法(静态方法)中super作为对象调用
在静态方法中super代表父类Animal,this指向子类Cat,在普通方法或者constructor中super代表Animal.prototype,this指向子类实例
class Animal{
constructor (){
this.name = "animal"
}
say(){
console.log(this.name);
}
static say(){ // Animal的私有方法
console.log(this.name);
}
}
class Cat extends Animal {
constructor(){
super()
this.name = "cat"
}
watch(){
super.say() // mimi
console.log(super.name); // undefined
}
static watch(){ // Cat的私有方法
// super.name="mimi" //Animal.name为只读属性
super.say() // Cat 父类子类默认有name属性
console.log(super.name); // Animal
}
}
Cat.watch()
console.log(Cat.name,Animal.name); // Cat Animal
创建对象的6种方式
翻了下之前做的学习笔记,作为记录,也为了回忆知识点。
创建对象和继承的有些原理很相似,放在一起记
这6种方式分别是:1. new Object() 2.字面量 3.工厂模式 4. 构造函数模式 5.原型模式 6. 混合模式
new操作符+Object
new Object() 直接返回一个对象
var person = new Object();
person.name = "lisi";
person.age = 21;
person.family = ["lida","lier","wangwu"];
person.say = function(){
alert(this.name);
}
字面量方式直接创建对象
var preson ={}
var person = {
name: "lisi",
age: 21,
family: ["lida","lier","wangwu"],
say: function(){
alert(this.name);
}
};
工厂模式
记忆点: 写一个方法将重复的代码封装起来
function createPerson(name,age,family) {
var o = new Object();
o.name = name;
o.age = age;
o.family = family;
o.say = function(){
alert(this.name);
}
return o;
}
//instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person1 = createPerson("lisi",21,["lida","lier","wangwu"]);
var person2 = createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object);//true
上述两种模式在创建多个类似对象时,会产生大量重复代码,工厂模式则能够解决这种问题
工厂模式解决了实例化多个对象的问题,但是没有解决对象识别的问题。实例化的对象的原型都是Object.prototype
构造函数模式
记忆点:在构造函数中挂载属性和方法
function Person(name,age,family) {
this.name = name;
this.age = age;
this.family = family;
this.say = function(){
alert(this.name);
}
}
//谁调用了这个函数,this就指向这个对象
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true
console.log(person1.constructor); //constructor 属性返回对创建
可以看出通过构造函数创建的对象知道自己从哪里来(instanceof)
当然构造函数也有缺点:
每个实例都包含不同的fucntion实例,虽然这些方法在做同一件事,但是实例化后产生了不同的对象。因此产生了原型模式。
原型模式
原理:创建一个构造函数Person,在其原型对象Person.prototype上挂载属性和方法,实例化出来的对象上就会继承原型身上的属性和方法。
记忆点:在原型对象上挂载属性和方法
function Person() {
}
Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
alert(this.name);
};
console.log(Person.prototype); //Object{name: 'lisi', age: 21, family: Array[3]}
var person1 = new Person(); //创建一个实例person1
console.log(person1.name); //lisi
var person2 = new Person(); //创建实例person2
person2.name = "wangwu";
person2.family = ["lida","lier","lisi"];
console.log(person2); //Person {name: "wangwu", family: Array[3]}
// console.log(person2.prototype.name); //报错
console.log(person2.age); //21
原型模式的好处就是所有实例对象共享他的属性和方法,此外还可以设置自己的属性(也叫私有属性),还可以覆盖原型对象上的同名方法。
缺点就是设置很多属性的时候很麻烦。
混合模式(构造函数模式+原型模式)
记忆点:在构造函数里面写属性,在原型上挂方法
混合模式是最完善的一种方式,既共享相同方法的引用,又保证了每个实例有自己的私有属性,最大程度节省了内存。
function Person(name,age,family){
this.name = name;
this.age = age;
this.family = family;
}
Person.prototype = {
constructor: Person, //每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
say: function(){
alert(this.name);
}
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
console.log(person1);
var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
console.log(person2);