概要:本篇博客主要介绍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. SQLite数据库操作 (原始操作)

    android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API, 使用该类可以完成对数据进行添加(Create).查询(Retrieve).更新(Update)和删除( ...

  2. 初试KONCKOUT+WEBAPI简单实现增删改查

    初试KONCKOUT+WEBAPI简单实现增删改查 前言 konckout.js本人也是刚刚接触,也是初学,本文的目的是使用ko和asp.net mvc4 webapi来实现一个简单增删改查操作.Kn ...

  3. java生成PDF文件(itext)

    itextpdf-5.4.3.jar下载地址: http://www.kuaipan.cn/file/id_58980483773788178.htm 导入itextpdf-5.4.3.jar ToP ...

  4. 关于Ajax无刷新分页技术的一些研究 c#

    关于Ajax无刷新分页技术的一些研究 c# 小弟新手,求大神有更好的解决方案,指教下~ 以前做项目,用过GridView的刷新分页,也用过EasyUI的封装好的分页技术,最近在老项目的基础上加新功能, ...

  5. 错排-HDU 2049 递推的应用

    当n个编号元素放在n个编号位置,元素编号与位置编号各不对应的方法数用M(n)表示,那么M(n-1)就表示n-1个编号元素放在n-1个编号位置,各不对应的方法数,其它类推. 第一步,把第n个元素放在一个 ...

  6. .Net程序员学用Oracle系列(3):数据库编程规范

    <.Net程序员学用Oracle系列:导航目录> 本文大纲 1.书写规范 1.1.大小写风格 1.2.缩进风格 1.3.换行 1.4.其它 2.命名规范 2.1.数据库对象命名 2.2.变 ...

  7. C语言之全局变量和局部变量

    全局变量和局部变量的简介(tips:很重要 牢记) 全局变量:就是定义在函数外的变量 全局变量可以在任意函数中使用 生命周期:程序一启动就开辟空间,直到程序退出才回收 全局变量不允许同名 局部变量:就 ...

  8. 高可用的池化 Thrift Client 实现(源码分享)

    本文将分享一个高可用的池化 Thrift Client 及其源码实现,欢迎阅读源码(Github)并使用,同时欢迎提出宝贵的意见和建议,本人将持续完善. 本文的主要目标读者是对 Thrift 有一定了 ...

  9. Java SE ——TCP协议网络编程(三)

    之前的代码中关闭了 socket 对象的输入流与输出流,但并没有关闭掉socket 对象,会造成服务器资源的浪费,应通过调用 socket 的 close() 方法来关闭当前的socket 对象. 因 ...

  10. ajax/fetch上传富文本时出现中文乱码的解决方案(百分号问题)

    最近正在编写自己的项目,其中遇到了nodejs后台接受到的富文本参数显示中文乱码的问题 一开始我以为是字符编码方式的错误,于是在请求参数的地方设置了utf-8,也就是: headers: { 'Con ...