学习继承之前自我感觉比较混乱的几个概念?
- 什么是构造函数?
- 什么是原型?什么是原型链?
- 什么是原型对象?什么是实例对象?
第一个:什么是构造函数?
在es5中构造函数在js中其实就是一个普通的函数,但是我们约定用new关键字创建实例的函数叫做构造函数。
在es6中其实用class声明的对象也是一个函数,class 不过是个语法糖
举个例子:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHi() {
console.log("hello world");
}
}
console.log(Person. prototype); //{constructor:f,sayHi:f,[[Prototype]]:Object}
console.log(Person .__proto__);//f(){}
let fn = function () {
this.a = 1;
this.b = 2;
};
console.log(fn.__proto__);//f(){}
console.log(fn.prototype);//{constructor:f,[[Prototype]]:Object}
第二个:什么是原型?
在js中原型也是一个对象,通过原型可以实现对象的属性继承。在js中存在一个[[Prototype]]属性,就是刚才构造函数的fn.prototype返回的内容中就可以看到该属性,js中这个属性对象的就是该对象的原型。但是[[Prototype]]是一个内部的属性不能够被访问,又但是在浏览器厂商实现了一个 __ proto__ 的非标准属性返回内容和[[Prototype]]一样,以此来获取对象的原型
什么是原型链?
即:构造函数的原型对象,其原型对象又有自己的原型对象对象,层层向上形成原型链,知道一个对象的原型对象为Null
举个例子:
原型链.png
第三个:什么是原型对象?
通俗来讲对象的.prototype属性就是获取该对象的,如第一段代码Person.prototype或者fn.prototype返回的是一个对象
什么是实例对象?
实例对象通过构造函数进行创建,再通俗来讲通过new 关键字创建的对象即可称之为实例对象。实例对象没有.prototype属性,因为实例对象它不是一个函数,只有函数才有protype属性
原型对象、原型、实例对象、构造函数之间有什么关系?
image.png
进入正题继承
实现继承有哪些方式?
es5中主要使用call/apply来实现继承,es6中使用extends关键字+super来实现
es5主要的几种实现方式,分为两大类:使用原型链继承、使用构造函数继承。
- 原型链继承:关键使用prototype属性来实现的继承。
- 缺点:原型中包含的引用值会在所有实例之间共享,只能在原型上修改属性和方法,因此派生出来的类不能传递自己的参数。
- 举个例子:
function Parent() {
this.name = 'Foo'
}
function Child() {}
// 继承 Parent
Child.prototype = new Parent();
let child1= new Child();
child1.name = 'Bar'
console.log(child1.name); // Bar
let child2= new Child();
console.log(child2.name); // Bar
- 构造函数继承:关键是使用call或者apply来实现
- 缺点:因为继承是写在子类里面的,函数不能进行复用;并且此时不能判别上一级的原型链,原型链断开了
- 好处:可以解决原型链继承原型中包含的引用值会在所有实例之间共享问题,并且,call后面也可以传递自己的参数
- 举个例子:
function Animal() {
this.species = "动物";
this.sleep = function () {
return this.species + "正在睡觉";
};
}
function Cat(name, color) {
Animal.call(this, arguments);
this.name = name;
this.color = color;
}
let cat = new Cat("小咪", "block");
console.log(cat);
cat.sleep();
- 组合继承(原型链+构造函数)
- 思路:使用原型链继承原型上的方法和属性,通过构造函数继承实例属性
- 举个例子
function Animal() {
this.species = "动物";
this.sleep = function () {
return this.species + "正在睡觉";
};
}
function Cat(name, color) {
Animal.call(this, arguments);
this.name = name;
this.color = color;
}
let animal = new Animal();
Cat.prototype = animal; //将构造函数的原型对象指向父类实例,实现对父类属性和方法的继承
Cat.__proto__ = Cat.constructor; //将构造函数的原型指向子类构造函数本身,实现子类特有的方法
let cat = new Cat("小咪", "block");
console.log(cat);
- 原型式继承(本质上是实现了一个浅复制,原型式继承-利用空对象进行继承:Object.create的实现方式)
- 缺点:本质上是实现对传入对象的引用,因此是浅复制,修改值会同步进行修改,存在共享属性问题
function Person(name) {
this.name = name;
this.sum = function () {
console.log("hello ,my name is" + this.name);
};
}
function content(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var sup = new Person("你叫啥");
var sup1 = new Person("我叫啥");
console.log(content(sup).name) //你叫啥
console.log(content(sup1).name) //你叫啥
- 寄生式继承(实现一个函数工厂,通过content浅拷贝对象,通过工厂函数定义自有属性和方法)
- 缺点:同上
function Person(name) {
this.name = name;
this.sum = function () {
console.log("hello ,my name is" + this.name);
};
}
function content(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var person= new Person("江卫国");
var person1= new Person("江卫民");
console.log(content(sup))
function subObject(obj) {
var sub = content(obj);
sub.name = "gar";
return sub;
}
var sup2 = subObject(person);
var sup3 = subObject(person1);
console.log(sup2)//gar,自身存在name属性不必往上一级进行查找
console.log(sup3)//gar
- 寄生式组合继承(寄生+构造函数)
- 可以解决构造出来不同实例的属性共享问题
- 缺点:效率问题,父类构造函数会被调用两次,一次是创建子类实例的时候,一次是在子类构造函数中调用的时候
function Person(name) {
this.name = name;
this.sum = function () {
console.log("hello ,my name is" + this.name);
};
}
function content(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var con = content(Person.prototype);
function Sub(name) {
Person.call(this, name); //第二次
}
Sub.prototype = con; //将子类的构造函数的实例指向构造出来的对象//第一次
con.constructor = Sub; //将构造出来的对象的构造函数指向子类,以此实现隔离不同实例之间的参数,由此我们也可以看出来只有实现了自己的构造函数才能进行自定义参数
var sub1 = new Sub("我是姜卫国");
var sub2 = new Sub("我是姜卫民");
console.log(sub1);//我是姜卫国
console.log(sub2);//我是姜卫民
参考:https://www.jianshu.com/p/b853d80b6c6a
《JavaScript高级程序设计(第4版)》
完结...
网友评论