深夜反而思维更活跃一些
(1)js中的原型链
js中存在一个名为原型链的机制,其特点如下
加入一个方法A,A方法一个属性为prototype,这个属性会指向某个对象,也成为A类对象的原型对象.
当我们根据A这个方法生成一个对象a,那么a的原型(proto)属性即为这个对象
a可以调用一些原型的属性和方法,另外,这也是一些方法查找的原理
(2)js相关的五种继承
继承一共有五种,这五种继承其实都是"属性追加"和"原型链"两个特殊功能的集合体,借用这两个特性实现继承的效果,下面讲解一下有关的思路和勘误
- 原型链继承
原型链继承指的就是,给创造对象的方法指定一个原型对象
//创建父类方法并且创造一个父类对象
function Parent(){
this.name="kevin"
}
let p=new Parent
//子类
function Child(){
}
//在创建方法这里指定继承的原型
Child.prototype=p
//创建两个对象
let c1=new Child()
let c2=new Child()
//则有
console.log(c1.__proto__)//p
console.log(c2.__proto__)//p
这种继承比较好理解,但是有个缺点就是图里面这种情况
console.log(c1.name) //"kevin"
console.log(c2.name) //"kevin"
这种情况下继承的原型是共有的,属性也是一致的,因为子类对象没有这种属性,所以会根据原型链网上寻找这种属性.
- 构造函数继承
构造函数继承在原理上很类似于其他oop语言的继承方式,即在构造子类对象的时候,优先构造父类对象.
//创建父类方法并且创造一个父类对象
function Parent(name){
this.name=name
}
function Child(name,age){
Parent.call(this,name)
this.age=age
}
//创建两个对象
let c1=new Child("张三",12)
console.log(c1.name) //"张三"
解释一下这段代码中出现的call语法,call语法是一种传递上下文的方法,在这里对于Parent构造方法使用了call,并且传入了参数this以及name,这个this就是Child的上下文对象
这个时候调用的Parent这个函数,里面的this指向的其实就算一个child对象了
这种继承方法无法像原型链或者es6新增的那种继承一样,只能算是一种"属性追加",这一点和后面的寄生继承一样
- 组合构造继承
把以上的两种方式给总和起来,一方面,使用构造函数的方法来给对象增加属性.
另一方面 ,使用原型链,给父类构造方法增加一个原型对象,这样可以达成这样的效果
//创建父类方法并且创造一个父类对象
function Parent(name){
this.name=name
}
Parent.prototype=A //对象A中有个属性address
function Child(name,age){
Parent.call(this,name)
this.age=age
}
//创建两个对象
let c1=new Child("张三",12)
let c2=new Child("李四",12)
//这个是不一样的
console.log(c1.name) //"张三"
console.log(c2.name) //"李四"
//这个是一样的
console.log(c1.address) //"一样的"
console.log(c2.address) //"一样的"
- 寄生继承
寄生继承和前面的借用构造函数继承一样,本质上不是利用父子类之间的关系,而是利用一些方法进行追加.区别在于,借用构造函数的追加方法是把上下文对象传入别的构造方法中进行追加
而寄生继承则是在一个方法内部,对传进来的方法,先生成对象,然后直接追加属性
function createObj(Obj){
let o=Object.create(Obj)
//直接对根据方法生成的对象o进行追加
return o
}
- 组合寄生继承(综合上面的方法)
举一个例子,这些东西的应用还是挺灵活的
使用构造函数的方法追加一些数据,然后使用寄生继承的方法增加原型链
//创建父类方法并且创造一个父类对象
function Parent(name){
this.name=name
}
//子类的方法中使用了借用构造函数继承
function Child(name){
Parent.call(this,name)
}
//创建一个,原型对象为o的类
function create(o){
function F(){}
F.prototype=o
return new F()
}
function Prototype(child, parent) {
var p = object(parent.prototype);
p.constructor = child;
child.prototype = p;
}
//给Child新建了一个原型对象,这个原型对象就是Parent的原型对象
Prototype(Child, Parent);
补充:棍鱼Prototype这个方法,可以这样子解释
首先,我们使用组合式继承,已经对Child函数扩展了属性,这种情况下,我们就没必要指定原型为Parent对象,Parent的原型有一个公共方法
我们在使用了一个空白对象F作为中间介质,避免了创建无用的Parent
区别画图展示
简单做个总结:
原型链是最贴近继承含义的,因为这东西会根据原型链向上找属性,属于纵向的
而另外两种,则是在原本的基础上扩展/追加属性,属于横向的,是一种伪继承
(3)一道和原型有关的例题(来自leetcode)
[leetcode]前端
这题对于基础要求...其实很高的,因为一般些js都自由散漫惯了..........真的
这题的输入是一个对象,一个生成方法,判断对象是否由这个方法或者其他方法生成
(1)方案1,也是我使用的一种,比较原型对象
首先先排除特殊情况,也就是对象为null或者方法为null,这种要返回false
var checkIfInstanceOf = function(obj, classFunction) {
if(obj==null || classFunction==null) return false;//第一种情况,对象和类均为空的时候
let a=Object.getPrototypeOf(obj); //先找到obj的原型对象
//然后开始遍历原型链
while(a!==null){
if(a === classFunction.prototype){
return true;
}
a=Object.getPrototypeOf(a);
}
return false;
}
(补充内容:除了可以用属性proto来获取原型,还可以直接使用函数Object.getPrototype(&&)来获取)
另外这个例题也带来了一个衍生的问题:
[如何判断一个对象属于哪个类,怎么实现?]
(1)判断基本类型可以使用函数typeof
但是这个函数有一个历史性的缺陷, typeof(null)===object......这确实有问题,但是已经没法改了
(2)利用原型链来判断 instanceof,实现原理和上面的题目是一样的
剩下还有别的一些方法