深入学习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 相关文章推荐
(JS实现)MapBar中坐标的加密和解密的脚本
May 16 Javascript
IE中createElement需要注意的一个问题
Jul 13 Javascript
jquery动态调整div大小使其宽度始终为浏览器宽度
Jun 06 Javascript
js数组去重的5种算法实现
Nov 04 Javascript
JS实现随机颜色的3种方法与颜色格式的转化
Jan 05 Javascript
深入浅析angular和vue还有jquery的区别
Aug 13 jQuery
Vuex中的State使用介绍
Jan 19 Javascript
iphone刘海屏页面适配方法
May 07 Javascript
vue组件间通信六种方式(总结篇)
May 15 Javascript
vue-cli随机生成port源码的方法
Sep 02 Javascript
JS实现拖动模糊框特效
Aug 25 Javascript
浅谈nuxtjs校验登录中间件和混入(mixin)
Nov 06 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
PHP Echo字符串的连接格式
2016/03/07 PHP
Windows Server 2008 R2和2012中PHP连接MySQL过慢的解决方法
2016/07/02 PHP
yii2缓存Caching基本用法示例
2016/07/18 PHP
JS的IE和Firefox兼容性集锦
2006/12/11 Javascript
JavaScript 设计模式之组合模式解析
2010/04/09 Javascript
Bookmarklet实现启动jQuery(模仿 云输入法)
2010/09/15 Javascript
根据选择不同的下拉值出现相对应的文本输入框
2013/08/01 Javascript
jQuery中offset()方法用法实例
2015/01/16 Javascript
javascript结合Canvas 实现简易的圆形时钟
2015/03/11 Javascript
简单实现js页面切换功能
2021/01/10 Javascript
AngularJS入门教程之路由机制ngRoute实例分析
2016/12/13 Javascript
js实现倒计时效果(小于10补零)
2017/03/08 Javascript
JavaScript算法学习之冒泡排序和选择排序
2019/11/02 Javascript
jquery向后台提交数组的代码分析
2020/02/20 jQuery
vue实现从外部修改组件内部的变量的值
2020/07/30 Javascript
基于Django的ModelForm组件(详解)
2017/12/07 Python
儿童学习python的一些小技巧
2018/05/27 Python
Windows系统下PhantomJS的安装和基本用法
2018/10/21 Python
详解Python给照片换底色(蓝底换红底)
2019/03/22 Python
python 自定义装饰器实例详解
2019/07/20 Python
matplotlib实现显示伪彩色图像及色度条
2019/12/07 Python
Pytorch 数据加载与数据预处理方式
2019/12/31 Python
利用setuptools打包python程序的方法步骤
2020/01/18 Python
Python+Dlib+Opencv实现人脸采集并表情判别功能的代码
2020/07/01 Python
python解释器安装教程的方法步骤
2020/07/02 Python
Python常用外部指令执行代码实例
2020/11/05 Python
微软加拿大官方网站:Microsoft Canada
2019/04/28 全球购物
澳大利亚实惠时尚女装商店:Katies
2019/06/16 全球购物
小区门卫工作职责
2013/12/14 职场文书
单位成立周年感言
2014/01/26 职场文书
中医学专业自荐信范文
2014/04/01 职场文书
中学教师个人总结
2015/02/10 职场文书
婚宴主持词
2015/06/30 职场文书
2016年党员学习廉政准则心得体会
2016/01/20 职场文书
MySQL数据库如何给表设置约束详解
2022/03/13 MySQL
MySql分区类型及创建分区的方法
2022/04/13 MySQL