持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
在开发的过程中,为了避免重复造轮子,我们希望可以尽可能地复用代码。继承是代码复用的一种方式,继承有助于合理地组织程序代码,将一个对象的属性扩展到另一个对象上。和面向对象语言有所不同,JS的继承是通过设置原型的引用实现的。每个对象的原型也拥有一个原型,以此类推,形成一个原型链。这个原型不是一个class,而是一个对象,即原型对象。
理解原型
在JavaScript中,对象是属性名与属性值的集合,例如
let person = {
sex:1,
work:{
first:ios,
second:web
},
action: function(){},
}
JS是动态语言,可以修改删除或者新增对象的属性
//删除工作属性
delete person.work;
//新增一个小宝宝
person.child = {sex:1}
当查找属性时,如果对象本身不具有该属性,则会查找原型上是否有该属性,就像Java等语言中,this->super->super...这中逻辑的查找。 比如下面的例子:
例一
let monkey = {
canrun : function(){
return true;
}
};
// 将对象monkey设置为person对象的原型,这样人就继承了猴子的属性,能跑了
Object.setPrototypeOf(person, monkey);
例二
let mySkill = { oc : true };
let skill1 = {swift: true };
let skill2 = {rn : true};
Object.setPrototypeOf(skill1, mySkill); // 将mySkill对象设置为skill1对象的原型
Object.setPrototypeOf(skill1, skill2); // 将skill1对象设置为skill2对象的原型
一个程序员最初他会的开发语言是oc,后面他进化了又学了swift,后面又进化了,又学会了rn。掌握skill2的前提(原型)是skill1,而掌握skill1的前提(原型)是mySkill。这样这个程序员就一步一步掌握了oc、swift、rn等技能。
函数原型
在JS中函数除了被调用之外,和普通对象一样也可以拥有属性,当然也有内置的原型对象,并且可以被修改。
原型和构造函数
- 每一个函数都有一个原型对象
- 每一个函数的原型都具有一个constructor属性,该属性指向函数本身
- 可以通过构造函数的原型中的constructor属性访问对象的构造函数
- contractor属性的原型设置为新创建的对象的原型
- 实例对象的与原型中有相同的function,则原型中的function会被覆盖
- 如果重写了constructor属性,那么原始值就被丢失了。
function iPhone(){} //定义一个空函数
iPhone.prototype.activate = function(){
return true;
};
let phone0 = iPhone();
let myphone = new iPhone();
iphone0是调用函数,但函数本身没有返回值,所以得到undefined;
myphone是通过new操作符调用iPhone函数,这样该函数就作为构造器来使用了。myphone就得到了新对象的引用,它是可以调用activeta函数激活这个iPhone的。
但有一点需要注意,由于JS的动态性如果在创建完myiphone之后,iPhone函数的prototype被重新赋值:
iPhone.prototype = {
haveFaceId : function(){
return true;
}
};
let herphone = new iPhone();
//可以通过一个对象的constructor来构造另一个对象,和new iPhone()效果一样,
//所以我们可以不知道某个对象的类型来创建相同类型的另一个对象。
let hisphone = new herphone.contructor();
新的赋值对myphone这个对象无效,herphone具有haveFaceId方法不具有activate方法。
总之,对象与函数原型之间的引用关系是在对象创建时建立的,新创建的对象将引用新的原型。旧对象所引用的原型还会被引用。
另外补充一点instanceof和typeof关键字的不同,大家可以通过代码感受下。instanceof能判断具体的对象是否为存在于原型链上的某个类型。例如下面继承部分讨论的代码中,用instanceof判断stu是否为Student/Person 都为true。但如果Student的原型直接被赋值为 {} ,则instanceof判断会失效
typeof只能判断是不是object。
myphone instanceof iPhone ;// true
typeof myphone == "object";// true
在浏览器验证结果如下:
实现继承
使用原型实现继承,最佳技术方案是SubClass.prototype = new SuperClass();
比如实现Student继承Person:
function Persson(){
sayHello : function(){
console.log("hello");
}
}
function Student(){
study: function(){
console.log("好好学习,天天向上")
}
}
Student.prototype = new Person();
let stu = new Student();
stu.sayHello();// hello
但这样简单操作原型实现继承的话会有个问题 stu.constructor == Student会不成立。我们可以通过 使用内置的Object.defineProperty方法设置对象属性的配置信息
Object.defineProperty(Student.prototype, "constructor", {
enumerable: false, //不可枚举
value: Student, //属性值为Student
writable: true
});
这样我们就使用原型完美的实现了继承!
其他方法
另外还有同学使用Student.prototype = Person.prototype方式来实现,但这里会出现一些副作用:这会导致在Person原型上发声的所有变化都被同步到Student原型上,所以不推荐用这种方法。
ES6 引入 class关键字
ECMAScript委员会对“模拟”基于类继承语法进行了标准化,在JS中使用class关键字,但底层的实现仍然是基于原型继承。
使用class 来修饰类需要显示的定义一个构造函数constructor,当使用new关键字调用类时会调用这个构造函数。
class是语法糖
class只是语法糖,class+extends 让JS模拟类的代码更为简洁和优雅。
ES6中继承
class Student extends Person {
constructor(){//可以传递初始化参数
}
}