概要:本篇博客主要介绍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 Server的数据加密简介

    防止开发人员获取到敏感数据(SQL Server的数据加密简介) 背景 有时候,我们还真的会碰到这样的需求:防止开发人员获取到敏感数据.也许你觉得很简单,把开发和运营分开不就可以了吗?是的,如果公司有 ...

  2. 指定url和深度的广度优先算法爬虫的python实现

    本文参考http://zoulc001.iteye.com/blog/1186996 广度优先算法介绍 整个的广度优先爬虫过程就是从一系列的种子节点开始,把这些网页中的"子节点"( ...

  3. C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)

    前言 近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条记录并压缩成562KB的归属地数据.我在互联网上搜索了相关文章,要不是数据库 ...

  4. touch事件分发

    touch事件分发 IOS事件分发 我们知道,如果要一个view(就是view,不是UIControl控件)能够响应事件操作,通常的做法是给该View加上相应的手势,或者重写和touch(当然也可以是 ...

  5. oracle exp、imp实现导出导入

    一.说明    oracle 的exp/imp命令用于实现对数据库的导出/导入操作; exp命令用于把数据从远程数据库服务器导出至本地,生成dmp文件; imp命令用于把本地的数据库dmp文件从本地导 ...

  6. 使用Guava进行函数式编程

    本文翻译自Getting Started with Google Guava这本书,如有翻译不足的地方请指出. 在这一章,我们开始注意到使用Guava进行编写代码会更加简单.我们将看看如何使用Guav ...

  7. (转)JS中公共/私有变量和方法

    私有变量 在对象内部使用'var'关键字来声明,而且它只能被私有函数和特权方法访问. 私有函数 在对象的构造函数里声明(或者是通过var functionName=function(){...}来定义 ...

  8. wp7之换肤原理简单分析

    wp7之换肤原理简单分析 纠结很久...感觉勉强过得去啦.还望各位大牛指点江山 百度找到这篇参考文章http://www.cnblogs.com/sonyye/archive/2012/03/12/2 ...

  9. 详解android的号码匹配

    什么是号码匹配,个人理解,即判断两组号码是否属于同一个号码.在实际使用过程中,接触到的号码会涉及到区号,国家编码以及IP号码等,这个时候就用到了号码匹配.两个内容不一样的号码,如+86***和1795 ...

  10. ssd可以用作redo 盘吗?

                                               ssd可以用作redo 盘吗? 1.ssd有写磨损,而且ssd的写性能也不是非常好,ssd只是随机读特别好,因为 ...