以为理解的很清楚了,遇到问题了,卧槽、把自己搞蒙了。
问题复现(都是模拟、并非源代码):
// 公用模块
var parent = function(option){
this.option = option;
}
parent.prototype = {
print:function(){
console.log(this.option.name);
}
}
// 功能模块调用
var child = function(option){
this.option = option;
return new parent(this.option);
}
child.prototype={
print:function(){
console.log(this.option.name);
}
}
// 功能中可能会有多个相同模块构建对象
var someModel = new child({name:"model_01"});
someModel.print(); // output:"model_01"
打印出来看起来正常,都怪子模块中有相同的方法而且逻辑输出一致,导致之前没发现。
现在往子模块中追加了一个方法:
child.prototype={
childPrint:function(){
console.log(this.option.age);
}
}
// 调用之后,终于报错了
var someModel = new child({name:"model_01",age:32});
someModel.childPrint();
// output: someModel.childPrint is not a function
原意是想在子模块中输出this.option.age
,可是报错方法未定义。在方法中打断点根本没有执行,并且查看对象someModel
中也没有这个方法,换了几种其他方式定义对象方法;
最后才想到自己理解的原型链是不是有问题,将方法定义在父模块中,卧槽!执行了!emmmm…
分析:
原本这样做的目的是想child
子类继承父类parent
方法,且子类有自己的私有方法childPrint
。问题就出在了return new parent();
上。这个所谓的子模块实际返回的是父模块的一个实例。等于没有了child
这一层,也就达不到我们想要自定私有方法的目的。
打印出对象console.log(someModel)
,输出的时parent
对象。
故再翻看一遍相关资料,进行总结。
继承与原型链
在JS中做继承只有一种结构对象,每一个实例对象都有一个__proto__
属性,这个__proto__
属性指向原型对象prototype
。
someModel.__proto__ == parent.prototype; // true
someModel.__proto__ == child.prototype ; // false
// 获取实例原型
Object.getPrototypeOf(someModel) == parent.prototype; // true
// 题外记,获取Object的原型对象,
// 会把Object当做构造函数处理。
Object.getPrototypeOf(Object) === Object.getPrototypeOf(Function) ;
// ƒ () { [native code] }
这也就是为什么上面的实例中访问不到childPrint
方法了。看之前打印的someModel
属于parent
实例。所以它指向parent
的原型对象,也就没有childPrint
方法了。
所有实例对象的原型对象都会指向
Object
,即都是Object
的实例。
注意原型链定义方法的写法
// 这样会破坏原型链,这种是重新定义了原型链。相当于重新声明覆盖!
parent.prototype = {
print:function(){
console.log(this.option.name);
}
}
// 这样写就是在原型链上添加定义了一个新的方法。保持了原型链原态
parent.prototype.print = function(){
console.log(this.option.name);
}
需要注意的几点:
- 原型链上的
this
指向问题; - 对象上的属性查找;
- 对象声明方式:
-
语法结构声明;引用类型:Array、Object、Function
let arr = [1,3,5]; // 继承了数组、对象的方法。 // arr - > Array.prototype -> Object.prototype -> null let obj = {name:"hello",age:32}; // 继承了对象的方法。 // obj -> Object.prototype -> null let fun = function(){}; // 继承了函数、对象的方法。 // fun -> Function.prototype - > Object.prototype -> null
-
使用
new
构造函数创建一个对象;像示例中那样。 -
Object.create()
创建一个新对象。第一个参数作为新对象的__prototype__
属性;第二个参数定义新对象的私有属性或修改对应属性、及属性描述符。 -
ES6的
class
,原型链上的语法糖。我之前的目的就是想实现继承extends
,并且拥有私有方法。
- 属性的遍历查找;确认自己想要查找的属性需要符合哪些要求,然后采用
Object
上的一些方法过滤一下,不要直接遍历。
继承示例
call()
、apply()
:调用父类,以便继承自身的属性;
Object.create()
:重写子类的prototype
手动继承父类的原型对象
修改文章开头的示例:
// 父类
var parent = function(option){
this.option = option;
// 增加一个name属性
this.name = "parent";
}
parent.prototype = {
print:function(){
console.log(this.option.name);
}
}
// 子类实现扩展
var child = function(option){
this.option = option;
// return new parent(this.option);
// call调用后传入的对象this,父类上的自身属性会继承。
parent.call(this,option);
}
call()、apply()会决定该方法执行的上下文环境,也就是定义了
this
的指向;方法中自身拥有的属性则会被继承。
还需要继承它的原型链。
// 实现父类上的原型对象上的方法继承。
child.prototype=Object.create(parent.prototype,{
age:{
value:21,
enumerable: true,
configurable: true,
writable: true
},
print:{ // override parent method
value:function(){
parent.prototype.print.apply(this,arguments);
// do more something...
console.log(this.age);
},
enumerable: true,
configurable: true,
writable: true
},
childPrint:{ // 定义自己的私有方法
value:function(){
console.log(this.option.age);
}
}
})
// 实例化一个对象,查看打印结果正常。
var someModel = new child({name:"model_01",age:32});
someModel.print();
// output:"model_01" 21
someModel.childPrint();
// output:32
Object.create()
第二个参数新增或修改属性时,属性的描述符中value或writable
和get或set
不能同时存在。
补一点属性描述符:
Object.defineProperties(obj, props)
多个属性的定义;
还有它的单数版:
Object.defineProperty(obj,prop,descripor)
定义单个属性的描述。
- 拥有
value
或writable
属于数据描述符; - 拥有
get
或set
属于存取描述符; - 默认是一个数据描述符。
有一个操作,可能需要在深入一点:
// 先输出一下child的原型对象构造函数
child.prototype.constructor; // ƒ Object() { [native code] }
// 感觉好像不对,类的构造函数不应该是它自身吗?
child.prototype.constructor = child;
// 现在输出一下,构造函数是它自身
child.prototype.constructor;
// ƒ (option){
this.option = option;
// return new parent(this.option);
parent.call(this,option);
}
疑问:在JS中类
的概念很弱,为什么还要讲究这个,构造函数,是因为ES6的class
吗
2019-03-31 17:21补充
扩展实践(模拟ES 6 class
模式)
-
不使用
prototype
,只使用call
/apply
;将所有的属性全部加载到this
上var parent = function(option){ this.name="parent"; this.print=function(){ console.log("parent:",this.name); } } var child = function(option){ // 先调用父类的构造函数、类似ES6 super(); // 这样基本接合 ES6 类的概念 parent.call(this,option); this.name="child"; } // 实例化 var model = new child({name:"model"});
同
class
继承、子类的的构造函数继承父类构造函数;子类没有自己的this
,我想应该是语法屏蔽,在这里我们可以在后面父类调用,只是name
属性被覆盖了。// 后调用父类方法 this.name="child"; parent.call(this,option);
类的构造函数是它自身:
child.prototype.constructor = child;
-
除了构造函数的继承,当然还需要原型链的继承了。使用
Object.create()
上面已经写过了,就不写了。讨论一些情况
如果我们不想父类,我们可能会直接继承父类的原型链:var parent = function(option){ this.name="parent"; } parent.prototype.print =function(){ console.log(this.name); } var child = function(){}; child.prototype = Object.create(parent.prototype); // 然后实例化 var model = new child({}); model.print(); // output:undefined
这个应该很清楚了,
this
指向的child
就是没有name
,我们再手动创建?
疯了吗,我为的就是不想继承构造函数,还想让我声明!!!!怎么办?可以再设计时可以在父类原型上定义属性,两次定义?怕什么,又不会覆盖。。。
parent.prototype = { name:"parent", print:function(){ console.log(this.name); } }
父类的有些属性,我们需要在原型链中定义,因为在构造函数中可能会被子类继承并覆盖掉。如果写到原型链中,按照原型链特性属性查找方式;可以确保我们特意想要从父类传给子类的属性。
-
可以进行多个类的继承,使用
Object.assign()
,mixin
模式var anotherParent = function(){}; Object.assign(child.prototype,anotherParent.prototype);
JS 原生对象原型不应该被扩展。尽可能的嵌套少一点,过于复杂导致性能问题。
参考:
属性的定义说的很楚。
2、《你不知道的JavaScript(上)》
使用过程的流程;涵盖的很清楚。