文档章节

捋一捋JavaScript对象的继承

Jill1231
 Jill1231
发布于 2017/07/19 14:50
字数 2615
阅读 2653
收藏 146
点赞 7
评论 6

许多oo语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承,其 实现继承主要是依靠原型链来实现的。

###原型链 基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法

简单回顾一下 构造函数 、 原型 和 实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针

function SuperType() {
    this.prototype = true;
}
SuperType.prototype.getSuperValue = function () {
    return this.prototype;
}

function SubType() {
    this.subprototype = false;
}

//继承
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
    return this.subprototype;
}

var instance = new SubType();
alert(instance.getSuperValue());//true

这个例子中的实例以及构造函数和原型之间的关系如下图 实例以及构造函数和原型之间的关系

所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。注意,我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType的实例,因此,这个新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType的原型。

instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然还在SuperType.prototype 中,但property则位于SubType.prototype 中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。 ####确定原型和实例的关系 1、使用 instanceof 操作符来测试 实例 与 原型链中出现过的构造函数,结果就会返回true

alert(instance instanceof Object);//true
alert(instance instanceof SuperType);//true
alert(insance instanceof subType);//true

2、使用isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此该方法也会返回true

alert(Object.prototype.isPrototypeOf(instance));//true
alert(SuperType.prototype.isPrototypeOf(instance));//true
alert(subType.prototype.isPrototypeOf(instance));//true

####谨慎的定义方法 1、给原型添加方法的代码一定要放在替换原型的语句之后,如下

//错误写法 错误写法 错误写法
function SuperType2() {
    this.prototype = true;
}
SuperType2.prototype.getSuperValue = function () {
    return this.prototype;
}

function SubType2() {
    this.subprototype = false;
}
// =====重写超类型中的方法=====
SubType2.prototype.getSuperValue = function () {
    return false;
}
//添加新方法
SubType2.prototype.getSubValue = function () {
    return this.subprototype;
}
//继承 
SubType2.prototype = new SuperType2();
//现在prototype被覆盖了,之前定义的getSubValue等方法都没了

var instance2 = new SubType2();
var instance21 = new SuperType2();
alert(instance2.getSuperValue());//true
alert(instance21.getSuperValue());//true

必须在用SuperType2的实例替换原型之后,再定义这两个方法

function SuperType1() {
    this.prototype = true;
}
SuperType1.prototype.getSuperValue = function () {
    return this.prototype;
}

function SubType1() {
    this.subprototype = false;
}

//继承  
SubType1.prototype = new SuperType1();
//添加新方法
SubType1.prototype.getSubValue = function () {
    return this.subprototype;
}
// 重写超类型中的方法
SubType1.prototype.getSuperValue = function () {
    return false;
}

var instance1 = new SubType1();
var instance12 = new SuperType1();
alert(instance1.getSuperValue());//false
alert(instance12.getSuperValue());//true

2、通过原型实现继承时,不能使用对象字面量创建原型的方法,因为这样做就会重写原型链

function SuperType3() {
    this.prototype = true;
}
SuperType3.prototype.getSuperValue = function () {
    return this.prototype;
}

function SubType3() {
    this.subprototype = false;
}

//继承
SubType3.prototype = new SuperType3();
//使用字面量添加新方法,会导致上一行代码无效
SubType3.prototype = {
    getSubValue:function () {
        return this.subprototype;
    },
    getSuperValue:function () {
        return false;
    }
}
var instance1 = new SubType3();
alert(instance3.getSuperValue());//error

分析:刚把SuperType3的实例赋值给原型,紧接着又将原型替换成一个对象字面量而导致的问题——现在的原型包含的是一个object的实例,而非SuperType3的实例

原型链的问题

1、包含引用类型值的原型

2、在创建子类型的实例时,不能向超类型的构造函数中传递参数,即没有办法在 不影响所有对象实例的情况下,给超类型的构造函数传递参数


###借用构造函数
基本思想:在子类型的构造函数内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因此通过apply()和call()方法也可以在(将来)新创建的对象上执行构造函数

function SuperType4() {
    this.colors = ["red","green","blue"];
}
function SubType4() {
    //继承
    SuperType4.call(this);
}
var instance4 = new SubType4();
instance4.colors.push("black");
alert(instance4.colors);//"red,green,blue,black"
var instance5 = new SubType4();
alert(instance5.colors);//"red,green,blue"

传递参数

function SuperType5(name) {
    this.name = name;
}
function SubType5(age) {
    SuperType5.call(this,"jill");
    this.age = age;
}
var instance5 = new SubType5(18);
alert(instance5.name);//jill
alert(instance5.age);//18

借用构造函数的问题

方法都在构造函数中定义因此函数复用就无从谈起了,而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式


###组合继承 ——也叫伪经典继承 思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,即通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性

function SuperType6(name) {
    this.name = name;
    this.colors = ["blue","red"];
}
SuperType6.prototype.sayName = function () {
    alert(this.name);
}

function SubType6(name,age) {
    SuperType6.call(this,name);                       //第二次调用SuperType6()
    this.age = age;
}

SubType6.prototype = new SuperType6();                //第一次调用SuperType6()
SubType6.prototype.constructor = SubType6;
SubType6.prototype.sayAge = function () {
    alert(this.age);
}

var instance6 = new SubType6("jill",18);
instance6.colors.push("yellow");
alert(instance6.colors);//blue,red,yellow
instance6.sayName();//jill
instance6.sayAge();//18

var instance7 = new SubType6("vane",22);
alert(instance7.colors);//blue,red
instance7.sayName();//vane
instance7.sayAge();//22

//instanceof()和isPrototypeOf()也能够用于识别基于组合继承创建的对象

组合继承的问题

无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部


###原型式继承 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

利用空对象作为中介。F是空对象,所以几乎不占内存。

function object(o) { //从本质讲,object()对传入其中的对象执行了一次浅复制
    function F() {}  //创建一个临时性的构造函数
    F.prototype = o; //将传入的对象作为这个构造函数的原型
    return new F();  //返回这个临时类型的新实例
}

var person = {
    name:"jill",
    friends:["vane","vicent"]
};

var person1 = object(person);
person1.name = "greg";
person1.friends.push("rob");

var person2 = object(person);
person2.name = "linda";
person2.friends.push("job");

alert(person.friends);//"vane,vicent,rob,job"
alert(person1.friends);//"vane,vicent,rob,job"
alert(person2.friends);//"vane,vicent,rob,job"

解析:这个新对象将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性,这意味着person.friends不仅属于person所有,而且也会被person1.friends和person2.friends共享 实际上这相当于又创建了person对象的两个副本

es5通过新增Object.create()方法规范了原型式继承。这个方法接收两个参数:新对象原型的对象,(可选的)新对象定义额外属性的对象

在传入一个参数的情况下,Object.create()与object()方法的行为相同

var person0 = {
    name:"jill",
    friends:["vane","vicent"]
};

//Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的
var person3 = Object.create(person0,{
    name:{
        value:"greg"
    },
    friends:{
        value:["a","b","c"]
    }
});
var person4 = Object.create(person0,{
    name:{
        value:"vanke"
    }
});
alert(person3.name);//"greg
alert(person3.friends);//"a","b","c"
alert(person4.friends);//"vane","vicent"

适合场景

在没有必要创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型继承是完全可以胜任的,但是注意:包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样


###寄生式继承 寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象

function createAnother(original) {
    //通过调用函数创建一个新对象  object()不是必须的,任何能够返回新对象的函数都适用于此模式
    var clone = object(original);
    clone.sayHi = function () {  //以某种方式来增强这个对象
        alert("Hi");
    }
    return clone;                //返回这个对象
}

var person = {
    name:"jill",
    friends:["vane","court"]
}

var antherPerson = createAnother(person);
antherPerson.sayHi();//Hi

适合场景

在主要考虑对象而不是自定义类型和构造函数的情况下

寄生式继承的问题

使用寄生式来为对象添加函数,会由于不能做到函数复用而降低效率,这一点于构造函数模式类似


###寄生组合式继承 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型的原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型

寄生组合式继承的基本模型如下

//接收两个参数:子类型构造函数和超类型构造函数
function inheritPrototype(subType,superType) {    
    //创建对象(创建超类型原型的一个副本)    
    var prototype = object(superType.prototype);   
    //增强对象(为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认constructor属性)   
    prototype.constructor = subType;      
    //指定对象(新创建的对象即副本赋值给子类型的原型)            
    subType.prototype = prototype;                   
}

function SuperType7(name) {
    this.name = name;
    this.colors = ["blue","red"];
}
SuperType7.prototype.sayName = function () {
    alert(this.name);
}

function SubType7(name,age) {
    SuperType6.call(this,name);                       //第一次调用SuperType6()
    this.age = age;
}

inheritPrototype(SubType7,SuperType7);

SubType7.prototype.sayAge = function () {
    alert(this.age);
}

这个例子解决了组合继承的问题,它只调用一次SuperType构造函数,并且因此避免了在subType.prototype上创建不必要的,多余的属性。同时,原型链还能保持不变,so还能正常使用instanceof和isPrototypeOf()

捋一捋JavaScript对象的继承 分享就结束啦,有错误的地方请大神指出哦,小妹立马改正。有兴趣可以接着看

相关文章

捋一捋JavaScript对象的理解

捋一捋JavaScript创建对象

© 著作权归作者所有

共有 人打赏支持
Jill1231

Jill1231

粉丝 107
博文 7
码字总数 13742
作品 0
深圳
前端工程师
加载中

评论(6)

haha123343
haha123343
实例都包含一个指向原型对象的内部指针 是不是说成 实例都包含一个指向其构造函数的原型对象的内部指针更好点
Jill1231
Jill1231

引用来自“loki_lan”的评论

又是你,消灭0评论!

回复@loki_lan :
Jill1231
Jill1231

引用来自“漆黑的烈焰使”的评论

以前看过,又不记得了
哈哈,我也是,所以重新捋一捋
开源中国首席PHP宣传专家
开源中国首席PHP宣传专家
以前看过,又不记得了
佛心看世界
佛心看世界
有点复杂。慢慢啃。
loki_lan
loki_lan
又是你,消灭0评论!
用小猪佩奇说明Javascript的原型和原型链

  没错,我就是标题党!你已经成功被我骗进来了。来都来了,那就聊聊再走呗!接下来就听我一本正经地跟你说道说道。   Javascript的原型是这门语言的一个重点和难点。看过很多大佬写的解...

志如 ⋅ 05/10 ⋅ 0

JavaScript由浅及深敲开原型链(一)

一.什么是js对象 1.简单理解js对象 在了解原型链之前,我们先要弄清楚什么是JavaScript的对象,JavaScript对象又由哪些组成。有人说一个程序就是一个世界,那么我们可以把对象称之为这个世界...

bb7bb ⋅ 05/15 ⋅ 0

将一个前端项目改写为chromo插件(一)

编写第一个chrome插件? 编写chrome插件完全就是前端知识加上一些专门的知识。 假设文件夹下有文件 嗯,现在看来他只是普通的html,其实,你只要在当前文件夹下加上文件,chrome浏览器就可以把...

saltfish666 ⋅ 05/27 ⋅ 0

浅谈JavaScript中的继承

近期,公司的业务处于上升期,对人才的需求似乎比以往任何时候都多。作为公司的前端,有幸窥探到了公司的前端面试题目,其中有一题大概是这样的(别激动,题目已经改了) 请用你自己的方式来...

lanzhiheng ⋅ 05/29 ⋅ 0

JS进阶(3):人人都能懂的继承

在上一篇文章中,我们主要介绍了 JavaScript 中原型对象的概念。这篇文章我们来聊一聊 JavaScript 中的继承。 一、继承的基本概念 相对于 JavaScript 来说,在其他一些面向对象的编程语言中,...

pantone44 ⋅ 05/09 ⋅ 0

5 分钟掌握 JavaScript 实用窍门

简评:一开始 JavaScript 只是为网页增添一些实时动画效果,现在 JS 已经能做到前后端通吃了,而且还是年度流行语言。本文分享几则 JS 小窍门,可以让你事半功倍 ~ 1. 删除数组尾部元素 一个...

⋅ 06/07 ⋅ 0

JavaScript核心概念归纳整理

原文出处: 熊俊漉 JavaScript语言本身是一个庞大而复杂的知识体系,复杂程度不低于任何一门后端语言,本文针对JavaScript语言的核心概念进行简单的梳理,对应的每个知识点仅仅点到为止,不作...

音乐宇Code ⋅ 05/27 ⋅ 0

django 1.8 官方文档翻译:5-2-2 表单素材 ( Media 类)

表单素材 ( 类) 渲染有吸引力的、易于使用的web表单不仅仅需要HTML – 同时也需要CSS样式表,并且,如果你打算使用奇妙的web2.0组件,你也需要在每个页面包含一些JavaScript。任何提供的页面...

apachecn_飞龙 ⋅ 2015/09/19 ⋅ 0

由js数组类型判断触发的浪漫思绪

一、前言 众所周知,js是门“动态”、“弱类型”编程语言,这意味着在js中可以很任性的定义变量,任性的同时也意味着需常在开发中对变量做类型判断,曾几何时,对数组变量的类型的判断是件很...

hanmin ⋅ 06/08 ⋅ 0

Lynx技术分析-JS引擎扩展设计

JS Binding 技术 Lynx(一个高效的跨平台框架) 的 JS Binding 技术最主要的目的是搭建一个高效的与 JS 引擎解耦的通信桥梁,同时具备 JS 引擎切换的能力。该技术经历了多次迭代,最终通过抽...

hxxft ⋅ 05/15 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Day 17 vim简介与一般模式介绍

vim简介 vi和Vim的最大区别就是编辑一个文件时vi不会显示颜色,而Vim会显示颜色。显示颜色更便于用户编辑,凄然功能没有太大的区别 使用 yum install -y vim-enhanced 安装 vim的三种常用模式...

杉下 ⋅ 51分钟前 ⋅ 0

【每天一个JQuery特效】根据可见状态确定是否显示或隐藏元素(3)

效果图示: 主要代码: <!DOCTYPE html><html><head><meta charset="UTF-8"><title>根据可见状态确定 是否显示或隐藏元素</title><script src="js/jquery-3.3.1.min.js" ty......

Rhymo-Wu ⋅ 今天 ⋅ 0

OSChina 周四乱弹 —— 初中我身体就已经垮了,不知道为什么

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @加油东溪少年 :下完这场雨 后弦 《下完这场雨》- 后弦 手机党少年们想听歌,请使劲儿戳(这里) @马丁的代码 :买了日本 日本果然赢了 翻了...

小小编辑 ⋅ 今天 ⋅ 12

浅谈springboot Web模式下的线程安全问题

我们在@RestController下,一般都是@AutoWired一些Service,由于这些Service都是单例,所以并不存在线程安全问题。 由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,...

算法之名 ⋅ 今天 ⋅ 0

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部