概要:本篇博客主要介绍JavaScript的原型

1.对象实例化

- 初始化的优先级

初始化操作的优先级如下:

  ● 通过原型给对象实例添加的属性

  ● 在构造器函数内给对象实例添加的属性

  在构造器内的绑定操作优先级永远都高于在原型上的绑定操作优先级。因为构造器的this上下文指向的是实例自身,所以我们可以在构造器内对核心内容执行初始化操作。

- 协调引用

  如下代码观察原型变化时的行为:

function Ninja(){
this.swung = true;
}
var ninja = new Ninja();
Ninja.prototype.swingSword = function(){
return this.swung;
};
assert(ninja.swingSword(),
"Method exists, even out of order.");

  在本例中,定义了一个构造器Ninja,并使用它创建了一个对象实例。然后 在原型上添加一个方法并运行测试。结果表明:原型上的属性并没有复制到其他地方,而是附加到新创建的对象上了,并可以和对象自身的属性引用一起协调运行。

  JavaScript中的每个对象,都有一个名为constructor的隐式属性,该属性引用的是创建该对象的构造器。由于prototype是构造器的一个属性,所以每个对象都有一种方式可以找到自己的原型。

  在控制台输入ninja.constructor引用时,我们看到的是所期望的Ninja()函数,这是因为该对象是使用此函数作为构造器进行创建的。一个更深层次的ninja.constructor.prototype.swingSword引用,说明了我们可以通过这种方式访问对象实例的原型属性。

  改变上述示例如下:

function Ninja(){
this.swung = true;
this.swingSword = function(){
return !this.swung;
};
} var ninja = new Ninja();
Ninja.prototype.swingSword = function(){
return this.swung;
};
console.assert(ninja.swingSword(),
"Called the instance method, not the prototype method.");

  在本例中,我们重新定义了一个和原型方法同名的实例方法,并在构造器执行之后才创建原型方法。测试表明,即便是在实例化对象之后,再在原型方法添加方法,实例方法也会优先考虑原型上的该方法。

- 通过构造器判断对象类型

  对象的构造器可以通过constructor属性获得。任何时候我们都可以重新引用该构造器,甚至可以使用它进行类型检查,示例如下:

function Ninja(){}
var ninja = new Ninja();
assert(typeof ninja == "object",
"The type of the instance is object.");
assert(ninja instanceof Ninja,
"instanceof identifies the constructor.");
assert(ninja.constructor == Ninja,
"The ninja object was created by the Ninja function.");

  使用typeof操作符判断该实例的类型,其检查结果并不是很有用,只是告诉我们该实例只是一个对象而已,因为检查结果总是返回object。而instanceof操作符,才是真正有用的,该操作符告诉了我们一个很明确的方法,来确定一个实例是否是由特定的函数构造器所创建。

  除此之外,我们还可以使用constructor属性,我们此时知道,该属性作为创建该对象的原始函数引用,被添加在所有的实例上。利用该属性,我们可以验证实例的起源。

function Ninja(){}
var ninja = new Ninja();
var ninja2 = new ninja.constructor();
assert(ninja2 instanceof Ninja, "It's a Ninja!");
assert(ninja !== ninja2, "But not the same Ninja!");

  定义一个构造器,然后使用该构造器创建一个实例。接着使用该实例的constructor属性再创建第二个实例。测试结果表明,成功创建了第二个Ninja对象,并且该变量指向的并不是同一个实例。

- 继承与原型链

  instanceof 操作符的另一个功能是作为对象继承的一种形式。如下代码:使用原型实现继承

function Person(){}
Person.prototype.dance = function(){};
function Ninja(){}
Ninja.prototype = {dance:Person.prototype.dance};
var ninja = new Ninja();
assert(ninja instanceof Ninja,
"ninja receives functionality from the Ninja prototype");
assert(ninja instanceof Person,"... and the Person prototype");
assert(ninja instanceof Object,"... and the Object prototype");

  由于函数的原型只是一个对象,所以有很多功能可以通过复制的方法达到继承的目的。在上述代码中,首先定义一个Person,然后再定义一个Ninja。因为Ninja显然也是一个人,所以我们希望Ninja也能继承Person的属性。我们尝试将Person原型的dance属性方法复制到Ninja的原型上作为同名方法来实现继承功能。测试结果表明,这是复制而不是继承。

  我们真正想要实现的是一个原型链,创建这样一个原型链最好的方式是,使用一个对象的实例作为另外一个对象的原型:SubClass.prototype = new SuperClass();

例如:Ninja.prototype = new Person();这将保持原型链,因为SubClass实例的原型将是SuperClass的一个实例,该实例不仅拥有原型,还持有SuperClass的所有属性,并且该原型指向其自身超类的一个实例。

function Person(){}
Person.prototype.dance = function(){};
function Ninja(){}
Ninja.prototype = new Person();
var ninja = new Ninja();
assert(ninja instanceof Ninja,
"ninja receives functionality from the Ninja prototype");
assert(ninja instaceof Person, "... and the Person prototype");
assert(typeof ninja.dance == "function","... and can dance!");

  上述代码将Person的一个实例作为Ninja的原型。通过执行instanceof操作,可以判断函数是否继承了其原型链中任何对象的功能。这种原型继承方式还有一个比较好的副作用是,所有原型中继承的函数都是实时更新的。

  所有原声JavaScript对象构造器(如Object、Array、String、Number、RegExp、Function)都有可以被操作和扩展的原型属性,这是有道理的,因为每个对象构造器自身就是一个函数。

2. 疑难陷阱

- 扩展对象

  我们也许会犯的及其严重的错误就是去扩展原生Object.prototype。其原因是,在扩展该原型时,所有的对象都会接受这些额外的属性。在我们遍历对象属性的时候,这个问题特别严重,新属性的出现可能会导致各种意想不到的行为。

object.prototype.keys = function(){
var keys = [];
for (var p in this) keys.push(p);
return keys;
};
var obj = {a: 1, b: 2, c: 3};
assert(obj.keys().length == 3,
"There are three prototypes in this object.");

  JavaScript提供一个名为hasOwnProperty()的方法,使用该方法可以确定一个属性是在对象实例上定义的,还是从原型里导入的。

Object.prototype.keys = function(){
var keys = [];
for (var i in this)
if (this.hasOwnProperty(i)) keys.push(i);
return keys;
};
var obj = {a: 1, b: 2, c: 3};
assert(obj.keys().length == 3,
"There are three properties in this object.");

  我们重新定义了方法,忽略了非实例属性。

- 扩展数字

  除了我们在上一节研究的Object,对其他原声对象的原型进行扩展相对来说比较安全。但是,另外一个有问题的原生对象是Number。

  在Number原型上添加一个方法

Number.prototype.add = function(num){
return this + num;
}
var n = 5;
assert(n.add(3) == 8,
"It works when the number is in a variable.");
assert((5).add(3) == 8,
"Also works if a number is wrapped in parentheses.");
assert(5.add(3) == 8,
"What about a simple literal?");

  这里,我们在Number上定义一个新方法add(),该方法接收一个参数,并将该参数与自身数值进行相加,然后再进行返回。然后用各种方式验证该新方法。但是,当我们尝试在浏览器中加载页面时,页面不会加载。一般来说,除非真的需要,最好避免在Number的原型上做扩展。

- 实例化问题

  函数有两种用途:作为"普通"的函数,以及作为构造器。因此容易混淆用法。

function User(first, last){
this.name = first + " " + last;
}
var user = User("Ichigo", "Kurosaki");
assert(user, "User instantiated");
assert(user.name == "Ichigo Kurosaki",
"User name correctly assigned");

  在上述代码中,我们定义一个User类,其构造器接受first和last参数,并将其连接形成一个完整的名称,再赋值给name属性。然后,我们创建一个该类的实例赋值给user变量,并测试该对象是否被实例化,以及构造器的执行是否正确。

  但运行代码时出现了严重的错误。原因是没有在User()函数上调用new操作符。这种情况下,new操作符的缺失会导致函数是作为普通函数进行调用的,而不是构造器,而且没有实例化一个新对象。除此之外,构造器函数如果作为普通函数进行调用的话,会产生微妙的副作用,如污染当前作用域,从而导致更多意想不到的结果。以下代码不小心将变量引入到全局命名空间中:

function User(first, last){
this.name = first + " " + last;
}
var name = "Rukia";
var user = User("Ichigo", "Kurosaki");
assert(name == "Rukia",
"Name was set to Rukia.");

  如下代码能够判断函数是否是作为构造器进行调用:

function Test(){
return this instanceof arguments.callee;
}
assert(!Test(), "We didn't instantiate, so it returns false.");
assert(new Test(), "We did instantiate, returning true.");

  函数在作为构造器进行执行的时候,表达式:

this.instanceof arguments.callee

  它的结果是true,如果作为普通函数执行,则返回true.

在调用者上修复该问题

function User(first, last){
if (!(this instanceof arguments.callee)){
return new User(first, last);
}
this.name = first + " " + last;
}
var name = "Rukia";
var user = User("Ichigo", "Kurosaki");
assert(name == "Rukia", "Name was set to Rukia.");
assert(user instanceof User, "User instantiated");
assert(user.name == "Ichigo Kurosaki",
"User name correctly assigned");

  判断函数是否按照不正确的方式进行了调用,如果是,则再实例化User自身,将其作为函数的结果进行返回。这样做的结果是,无论我们函数调用的时候是否作为普通函数进行调用,最终都返回一个User实例。

JavaScript忍者秘籍——原型的更多相关文章

  1. JavaScript忍者秘籍——函数(下)

    概要:本篇博客主要介绍函数的一些类型以及常见示例 1.匿名函数 使用匿名函数的常见示例: window.onload = function(){ assert(true,'power!'); }; / ...

  2. JavaScript忍者秘籍——运行时代码求值

    1. 代码求值机制 JavaScript中,有很多不同的代码求值机制. ● eval()函数 ● 函数构造器 ● 定时器 ● <script>元素 - 用eval()方法进行求值 作为定义 ...

  3. JavaScript忍者秘籍——驯服线程和定时器

    1.定时器和线程 - 设置和清除定时器 JavaScript提供了两种方式,用于创建定时器以及两个相应的清除方法.这些方法都是window对象上的方法. 方法 格式 描述 setTimeout   i ...

  4. JavaScript忍者秘籍——闭包

    概要:本篇博客主要介绍了JavaScript的闭包 1.闭包的工作原理 简单地说,闭包就是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域. 例如: var outerVa ...

  5. JavaScript忍者秘籍——函数(上)

    概要:本篇博客主要介绍了JavaScript的函数. 1.第一型对象 JavaScript的函数可以像对象一样引用,因此我们说函数是第一型对象.除了可以像其他对象类型一样使用外,函数还有一个特殊的功能 ...

  6. 深入理解JavaScript中的函数操作——《JavaScript忍者秘籍》总结

    匿名函数 对于什么是匿名函数,这里就不做过多介绍了.我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性.通常,匿名函数的使用情况是:创建一个供以后使用的函数.简单的 ...

  7. javascript 忍者秘籍读书笔记(二)

    闭包的私有变量 function Ninja() { let feints = 0; this.getFeints = function () { return feints }; this.fein ...

  8. javascript 忍者秘籍读书笔记

    书名 "学徒"=>"忍者" 性能分析 console.time('sss') console.timeEnd('sss') 函数 函数是第一类对象 通过字 ...

  9. 【JavaScript忍者秘籍】定时器

随机推荐

  1. sql 清除日志空间

    USE DBCenter GO SELECT file_id, name FROM sys.database_files; 查找日志名称 USE DBCenter ; GO ALTER DATABAS ...

  2. MS数据库优化查询最常见的几种方法

    1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存不足 5.网络速度慢 6.查询出的数据量过大 ...

  3. SQLSERVER到底能识别多少个逻辑CPU?

    SQLSERVER到底能识别多少个逻辑CPU? SQLSERVER到底能识别多少个逻辑CPU? 前言 在前几天在论坛有人遇到SQLSERVER识别逻辑CPU的问题 帖子地址: http://socia ...

  4. PL/SQL配置大小写转换等快捷键

    Tools(工具) --> Preferences(首选项) --> Key Configuration(键配置) 然后修改自己需要的快捷键方式 作者:itmyhome

  5. 配置restful webservice 框架restkit

    (配置restful webservice 框架restkit) RestKit 是一个开源的 objective-c 框架,容许在 iOS 和 Mac OS X 的 Objective-C 中与 R ...

  6. No object in the CompoundRoot has a publicly accessible property named

    No object in the CompoundRoot has a publicly accessible property named 'typeid' (no setter could be ...

  7. 基于easyui的webform扩展(续)

    基于easyui的webform扩展(续) 回顾 <前端基于easyui的mvc扩展>.<前端基于easyui的mvc扩展(续)>.<基于easyui的webform扩展 ...

  8. 页面缓存OutputCache

    更新页面缓存OutputCache   为什么要使用OutputCache 我认为OutputCache是最简单的缓存技术了,它针对的是页面级别的,简单的一条指令就可以达到缓存的效果,有效的减轻服务器 ...

  9. NDepend 3.0已与Visual Studio集成

    NDepend 3.0已与Visual Studio集成 投递人 itwriter 发布于 2010-02-10 16:17 评论(0) 有1638人阅读  原文链接  [收藏]  « » NDepe ...

  10. php5.3.*编译出现make: *** [ext/gd/libgd/gd_compat.lo] Error 1 解决方法

     升级系统,把php5.2.9升级到5.3.6,按照以前的编译参数,configure能正常通过,但是在make的时候提示: In file included from /root/php-5.3.6 ...