深入学习JavaScript中的原型prototype


Posted in Javascript onAugust 13, 2015

javascript 是一种 prototype based programming 的语言, 而与我们通常的 class based programming 有很大 的区别,我列举重要的几点如下:

1.函数是first class object, 也就是说函数与对象具有相同的语言地位
2.没有类,只有对象
3.函数也是一种对象,所谓的函数对象
4.对象是按 引用 来传递的
那么这种 prototype based programming 的语言如何实现继承呢(OO的一大基本要素), 这也便是 prototype 的由来.

看下面的代码片断:

function foo(a, b, c)
{
return a*b*c;
}
alert(foo.length);
alert(typeof foo.constructor);
alert(typeof foo.call);
alert(typeof foo.apply);
alert(typeof foo.prototype);

对于上面的代码,用浏览器运行后你会发现:

1.length: 提供的是函数的参数个数
2.prototype: 是一个object
3.其它三个都是function
而对于任何一个函数的声明,它都将会具有上面所述的5个property(方法或者属性).

下面我们主要看下prototype.

// prototype
function Person(name, gender)
{
this.name = name;
this.gender = gender;
this.whoAreYou = function(){//这个也是所谓的closure, 内部函数可以访问外部函数的变量
var res = "I'm " + this.name + " and I'm a " + this.gender +".";
return res;
};
}
// 那么在由Person创建的对象便具有了下面的几个属性
Person.prototype.age = 24;
Person.prototype.getAge = function(){
return this.age;
};
flag = true;
if (flag)
{
var fun = new Person("Tower", "male");
alert(fun.name);
alert(fun.gender);
alert(fun.whoAreYou());
alert(fun.getAge());
}
Person.prototype.salary = 10000;
Person.prototype.getSalary = function(){
return this.name + " can earn about " + this.salary + "RMB each month." ;
};
// 下面就是最神奇的地方, 我们改变了Person的prototype,而这个改变是在创建fun之后
// 而这个改变使得fun也具有了相同的属性和方法
// 继承的意味即此
if (flag)
{
alert(fun.getSalary());
alert(fun.constructor.prototype.age);//而这个相当于你直接调用 Person.prototype.age
alert(Person.prototype.age);
}

从上面的示例中我们可以发现,对于prototype的方法或者属性,我们可以 动态地 增加, 而由其创建的 对象自动会 继承 相关的方法和属性.

另外,每个对象都有一个 constructor 属性,用于指向创建其的函数对象,如上例中的 fun.constructor 指向的 就是 Person.

那么一个疑问就自然产生了, 函数对象中自身声明的方法和属性与prototype声明的对象有什么差别?

有下面几个差别:

1.自身声明的方法和属性是 静态的, 也就是说你在声明后,试图再去增加新的方法或者修改已有的方法,并不会 对由其创建的对象产生影响, 也即 继承 失败
2.而prototype可以动态地增加新的方法或者修改已有的方法, 从而是 动态的 ,一旦 父函数对象 声明了相关 的prototype属性,由其创建的对象会 自动继承 这些prototype的属性.
继续上面的例子:

flag = true;
// 函数内部声明的方法是静态的,无法传递的
Person.school = "ISCAS";
Person.whoAreYou = function(){
return "zhutao";
};//动态更改声明期的方法,并不会影响由其创建的对象的方法, 即所谓的 静态
if (flag)
{
alert(Person.school);
alert(fun.school);//输出的是 "undefined"
alert(Person.whoAreYou()); //输出 zhutao
alert(fun.whoAreYou()); // I'm Tower and I'm a male.
}
Person.prototype.getSalary = function(){
return "I can earn 1000000 USD";
};
if (flag)
{
alert(fun.getSalary());//已经继承了改变, 即所谓的 动态
}

既然有函数对象本身的属性, 也有prototype的属性, 那么是由其创建的对象是如何搜索相应的属性的呢?

基本是按照下面的流程和顺序来进行.

1.先去搜索函数对象本身的属性,如果找到立即执行
2.如果1没有找到,则会去搜索prototype属性,有2种结果,找到则直接执行,否则继续搜索 父对象 的 父对象 的prototype, 直至找到,或者到达 prototype chain 的结尾(结尾会是Object对象)
上面也回答如果函数对象本身的属性与prototype属性相同(重名)时的解决方式, 函数本身的对象 优先 .

prototype 的典型示例

用过 jQuery 或者 Prototype 库的朋友可能知道,这些库中通常都会有 trim 这个方法。

示例:

String.prototype.trim = function() {
 return this.replace(/^\s+|\s+$/g, '');
};

trim 用法:

' foo bar '.trim(); // 'foo bar'

但是这样做又有一个缺点,因为比较新版本的浏览器中的 JavaScript 引擎在 String 对象中本身就提供了 trim 方法, 那么我们自己定义的 trim 就会覆写它自带的 trim。其实,我们在定义 trim 方法之前,可以做个简单的检测,看是否需要自己添加这个方法:

if(!String.prototype.trim) {
 String.prototype.trim = function() {
 return this.replace(/^\s+|\s+$/g, '');
 };
}

原型链

JavaScript 中定义或实例化任何一个对象的时候,它都会被附加一个名为 __proto__ 的隐藏属性,原型链正是依靠这个属性才得以形成。但是千万别直接访问 __proto__ 属性,因为有些浏览器并不支持直接访问它。另外 __proto__ 和 对象的 prototype 属性也不是一回事,它们各自有各自的用途。

怎么理解呢?其实,当我们创建 myObject 函数时,实际上是创建了一个 Function 类型的对象:

console.log(typeof myObject); // function

这里要说明一下,Function 是 JavaScript 中预定义的一个对象,所以它也有自己预定义的属性(如 length 和 arguments)和方法(如 call 和 apply),当然也有 __proto__,以此实现原型链。也就是说,JavaScript 引擎内可能有类似如下的代码片段:

Function.prototype = {
 arguments: null,
 length: 0,
 call: function() {
 // secret code
 },
 apply: function(){
 // secret code
 },
 ...
};

事实上,JavaScript 引擎代码不可能这样简单,这里只是描述一下原型链是如何工作的。

我们定义了一个函数 myObject,它还有一个参数 name,但是并没有给它任何其它属性,例如 length 或者其它方法,如 call。那么下面这段代码为啥能正常执行呢?

console.log(myObject.length); // 结果:1,是参数的个数

这是因为我们定义 myObject 时,同时也给它定义了一个 __proto__ 属性,并赋值为 Function.prototype(参考前面的代码片段),所以我们能够像访问其它属性一样访问 myObject.length,即使我们并没有定义这个属性,因为它会顺着 __proto__ 原型链往上去找 length,最终在 Function 里面找到了。

那为什么找到的 length 属性的值是 1,而不是 0 呢,是什么时候给它赋值的呢?由于 myObject 是 Function 的一个实例:

console.log(myObject instanceof Function); // true
console.log(myObject === Function); // false

当实例化一个对象的时候,对象的 __proto__ 属性会被赋值为其构造者的原型对象,在本示例中就是 Function,此时构造器回去计算参数的个数,改变 length 的值。

console.log(myObject.__proto__ === Function.prototype); // true

而当我们用 new 关键字创建一个新的实例时,新对象的 __proto__ 将会被赋值为 myObject.prototype,因为现在的构造函数为 myObject,而非 Function。

var myInstance = new myObject('foo');
console.log(myInstance.__proto__ === myObject.prototype); // true

新对象除了能访问 Function.prototype 中继承下来的 call 和 apply 外,还能访问从 myObject 中继承下来的 getName 方法:

console.log(myInstance.getName()); // foo
 
var mySecondInstance = new myObject('bar');
 
console.log(mySecondInstance.getName()); // bar
console.log(myInstance.getName()); // foo

其实这相当于把原型对象当做一个蓝本,然后可以根据这个蓝本创建 N 个新的对象。

再看一个多重prototype链的例子:

// 多重prototype链的例子
function Employee(name)
{
this.name = "";
this.dept = "general";
this.gender = "unknown";
}
function WorkerBee()
{
this.projects = [];
this.hasCar = false;
}
WorkerBee.prototype = new Employee; // 第一层prototype链
function Engineer()
{
this.dept = "engineer"; //覆盖了 "父对象"
this.language = "javascript";
}
Engineer.prototype = new WorkerBee; // 第二层prototype链
var jay = new Engineer("Jay");
if (flag)
{
alert(jay.dept); //engineer, 找到的是自己的属性
alert(jay.hasCar); // false, 搜索到的是自己上一层的属性
alert(jay.gender); // unknown, 搜索到的是自己上二层的属性
}

上面这个示例的对象关系如下:

深入学习JavaScript中的原型prototype

Javascript 相关文章推荐
JavaScript 对象的属性和方法4种不同的类型
Mar 19 Javascript
Extjs中DisplayField的日期或者数字格式化扩展
Sep 03 Javascript
jquery中html、val与text三者属性取值的联系与区别介绍
Dec 29 Javascript
JavaScript模块化开发之SeaJS
Dec 13 Javascript
jquery解析XML及获取XML节点名称的实现代码
May 18 Javascript
Bootstrap文件上传组件之bootstrap fileinput
Nov 25 Javascript
Angular的模块化(代码分享)
Dec 26 Javascript
Ionic + Angular.js实现图片轮播的方法示例
May 21 Javascript
微信小程序如何获取用户信息
Jan 26 Javascript
Vue路由管理器Vue-router的使用方法详解
Feb 05 Javascript
JS实现iframe中子父页面跨域通讯的方法分析
Mar 10 Javascript
详解为什么Vue中不要用index作为key(diff算法)
Apr 04 Javascript
javascript获取本机操作系统类型的方法
Aug 13 #Javascript
javascript中offset、client、scroll的属性总结
Aug 13 #Javascript
用JavaScript实现PHP的urlencode与urldecode函数
Aug 13 #Javascript
asp.net中oracle 存储过程(图文)
Aug 12 #Javascript
JavaScript的jQuery库插件的简要开发指南
Aug 12 #Javascript
JavaScript中的call方法和apply方法使用对比
Aug 12 #Javascript
详细解读JavaScript的跨浏览器事件处理
Aug 12 #Javascript
You might like
BBS(php & mysql)完整版(六)
2006/10/09 PHP
php上传图片存入数据库示例分享
2014/03/11 PHP
PHP中执行cmd命令的方法
2014/10/11 PHP
php抛出异常与捕捉特定类型的异常详解
2016/10/26 PHP
jcarousellite.js 基于Jquery的图片无缝滚动插件
2010/12/30 Javascript
replace()方法查找字符使用示例
2013/10/28 Javascript
验证控件与Button的OnClientClick事件详细解析
2013/12/04 Javascript
jsPDF生成pdf后在网页展示实例
2014/01/16 Javascript
Javascript冒泡排序算法详解
2014/12/03 Javascript
js实现头像图片切割缩放及无刷新上传图片的方法
2015/07/17 Javascript
JS和jQuery使用submit方法无法提交表单的原因分析及解决办法
2016/05/17 Javascript
JS中的数组方法笔记整理
2016/07/26 Javascript
JS实现的简单表单验证功能示例
2017/10/13 Javascript
vue中rem的配置的方法示例
2018/08/30 Javascript
从零开始用electron手撸一个截屏工具的示例代码
2018/10/10 Javascript
简述vue路由打开一个新的窗口的方法
2018/11/29 Javascript
微信小程序基于canvas渐变实现的彩虹效果示例
2019/05/03 Javascript
Element ui 下拉多选时新增一个选择所有的选项
2019/08/21 Javascript
layui table设置某一行的字体颜色方法
2019/09/05 Javascript
[01:13]2014DOTA2西雅图邀请赛 舌尖上的TI4
2014/07/08 DOTA
忘记ftp密码使用python ftplib库暴力破解密码的方法示例
2014/01/22 Python
python图像处理之反色实现方法
2015/05/30 Python
Python 实现王者荣耀中的敏感词过滤示例
2019/01/21 Python
对Python 多线程统计所有csv文件的行数方法详解
2019/02/12 Python
Python在终端通过pip安装好包以后在Pycharm中依然无法使用的问题(三种解决方案)
2020/03/10 Python
keras .h5转移动端的.tflite文件实现方式
2020/05/25 Python
python interpolate插值实例
2020/07/06 Python
Python抖音快手代码舞(字符舞)的实现方法
2021/02/07 Python
任意一块网页内容实现“活”的背景(目前火狐浏览器专有)
2014/05/07 HTML / CSS
《跨越海峡的生命桥》教学反思
2014/02/24 职场文书
80后职场人的职业生涯规划
2014/03/08 职场文书
洗手间标语
2014/06/23 职场文书
副检察长四风问题对照检查材料思想汇报
2014/10/07 职场文书
党的群众路线教育实践活动个人批评与自我批评
2014/10/16 职场文书
2014年社区民政工作总结
2014/12/02 职场文书
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技