27、理解js的继承机制(转载自阮一峰)
Javascript继承机制的设计思想
作者: 阮一峰
日期: 2011年6月 5日
我一直很难理解Javascript语言的继承机制。
它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。
我花了很多时间,学习这个部分,还做了很多笔记。但是都属于强行记忆,无法从根本上理解。
直到昨天,我读到法国程序员Vjeux的解释,才恍然大悟,完全明白了Javascript为什么这样设计。
下面,我尝试用自己的语言,来解释它的设计思想。彻底说明白prototype对象到底是怎么回事。其实根本就没那么复杂,真相非常简单。
一、从古代说起
要理解Javascript的设计思想,必须从它的诞生说起。
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有让服务器端判断。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。
因此,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。工程师Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。
1994年正是面向对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。
Brendan Eich无疑受到了影响,Javascript里面所有的数据类型都是对象(object),这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢?
二、Brendan Eich的选择
如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
他考虑到,C++和Java语言都使用new命令,生成实例。
C++的写法是:
ClassName *object = new ClassName(param);
Java的写法是:
Foo foo = new Foo();
因此,他就把new命令引入了Javascript,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?
这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
举例来说,现在有一个叫做DOG的构造函数,表示狗对象的原型。
function DOG(name){
this.name = name;
}
对这个构造函数使用new,就会生成一个狗对象的实例。
var dogA = new DOG('大毛');
alert(dogA.name); // 大毛
注意构造函数中的this关键字,它就代表了新创建的实例对象。
三、new运算符的缺点
用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。
比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。
function DOG(name){
this.name = name;
this.species = '犬科';
}
然后,生成两个实例对象:
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。
dogA.species = '猫科';
alert(dogB.species); // 显示"犬科",不受dogA的影响
每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。
四、prototype属性的引入
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
还是以DOG构造函数为例,现在用prototype属性进行改写:
function DOG(name){
this.name = name;
}
DOG.prototype = { species : '犬科' };
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
alert(dogA.species); // 犬科
alert(dogB.species); // 犬科
现在,species属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。
DOG.prototype.species = '猫科';
alert(dogA.species); // 猫科
alert(dogB.species); // 猫科
五、总结
由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。
这就是Javascript继承机制的设计思想。
27、理解js的继承机制(转载自阮一峰)的更多相关文章
- 深入理解 JS 引擎执行机制(同步执行、异步执行以及同步中的异步执行)
首先明确两点: 1.JS 执行机制是单线程. 2.JS的Event loop是JS的执行机制,深入了解Event loop,就等于深入了解JS引擎的执行. 单线程执行带来什么问题? 在JS执行中都是单 ...
- 转载:阮一峰 RESTful API设计指南
阮一峰先生的文章对我理解启发很大,转载到我的博客 http://www.ruanyifeng.com/blog/2014/05/restful_api.html 网络应用程序,分为前端和后端两个部分. ...
- 转载自-阮一峰-测试框架 Mocha 实例教程
测试框架 Mocha 实例教程 作者: 阮一峰 日期: 2015年12月 3日 Mocha(发音"摩卡")诞生于2011年,是现在最流行的JavaScript测试框架之一,在浏 ...
- 初步理解JS的事件机制
一.事件流(捕获,冒泡) 事件流:指从页面中接收事件的顺序,有冒泡流和捕获流. 当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体顺序是怎样的呢?冒 ...
- 深入理解 js this 绑定机制
函数调用位置 与词法作用域相反的是,this的指向由函数运行时决定,它是动态的,随着函数调用位置变化而变化. 要理解 this,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而 不是声明的 ...
- js 面向对象 继承机制
根据w3cschool上的描述:共有3种继承方法(对象冒充,原型链,混合) 1.对象冒充:构造函数ClassA使用this关键字给所有属性和方法赋值,使ClassA构造函数成为ClassB的方法,调用 ...
- 转载:阮一峰 理解RESTful架构
转载 http://www.ruanyifeng.com/blog/2011/09/restful.html 越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件&q ...
- (转)Javascript面向对象编程(三):非构造函数的继承(作者:阮一峰)
不使用构造函数实现"继承". 一.什么是"非构造函数"的继承? 比如,现在有一个对象,叫做"中国人". var Chinese = { na ...
- (转)Javascript面向对象编程(二):构造函数的继承(作者:阮一峰)
对象之间的"继承"的五种方法. 比如,现在有一个"动物"对象的构造函数. function Animal(){ this.species = "动物& ...
随机推荐
- Cesium.js学习第一天(设置材质)
var viewer = new Cesium.Viewer('cs'); var entity = viewer.entities.add({ position: Cesium.Cartesian3 ...
- 【洛谷P1983】车站分级
车站分级 题目链接 首先,可以发现火车停靠站点的大小是没有什么规律的, 火车可以停靠在级别<=当前级别的站点,必须停靠在级别>=当前最高级别的站点 但是所有没有被停靠的站点级别一定比所有被 ...
- 【luogu P3952 时间复杂度】 题解
对于2017 D1 T2 这道题 实实在在是个码力题,非常考验耐心. 其实大体的思路并不是非常难想出来,但是要注意的小细节比较多. 题目链接:https://www.luogu.org/problem ...
- while、dowhile、switchcase 循环嵌套、穷举、迭代
for(var i=0;i<5;i++)//假如规定到5 { document.write("123456<br/>") } 如果变成这样 var i=0: fo ...
- swiper插件使用技巧
1.加载插件: <!DOCTYPE html> <html> <head> ... <link rel="stylesheet" href ...
- 使用classList来实现两个按钮样式的切换
classList属性的方法:add();remove();toggle(); 描述,在一些页面我们需要使用两个按钮来回切换,如图: 我们要使用到add()和remove()方法 html部分: &l ...
- [Linux/Unix]用户和用户组管理
Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统的用户,都必须拥有自己的账号. 实现用户的管理,主要做: 用户账号的添加.删除.修改: 用户口令的管理: 用户组的管理. (一)用户的 ...
- Lucas定理及应用
额,前两天刚讲了数据结构,今天我来讲讲组合数学中的一种奇妙优化——Lucas 先看这样一个东西 没学过lucas的肯定会说:还不简单?处理逆元,边乘边膜呗 是,可以,但注意一下数据范围 你算这一次,你 ...
- string类中字符的大小写转换
今天做一道题,要用string类,涉及大小写转换,查看了C++文档,string类没有提供这样的方法,只好自己写. 之后是想到一个比较笨的方法,我把string当成一个容器,然后用迭代器一个一个来替换 ...
- MyEclipse格式化JSP代码,其中Javascript无法格式化的原因
MyEclipse格式化JSP代码,其中Javascript无法格式化的原因: 可能是JSP页面代码有错误的地方,而且可能是一个很微小的错误,比如多写了一个标点符号,这个需要仔细检查,包括HTML.C ...