`
wind2154
  • 浏览: 51229 次
  • 性别: Icon_minigender_1
  • 来自: 泉州
社区版块
存档分类
最新评论

理解JavaScript原型

阅读更多

 注:本文来自微博网友@三月沙 的翻译投稿。

javascript原型总会给人产生一些困惑,无论是经验丰富的专家,还是作者自己也时常表现出对这个概念某些有限的理解,我认为这样的困惑在我们一开始接触原型时就已经产生了,它们常常和new、constructor相关,特别是函数(function)的原型(prototype)属性(property)。事实上,原型是一种非常简单的概念。为了更好的理解它,我们应该首先记住这个原则,那就是忘记我们已经学到的关于构造原型(construtor prototypes)的认识。

什么是原型?

原型是一个对象,其他对象可以通过它实现属性继承。

任何一个对象都可以成为原型么?

哪些对象有原型

所有的对象在默认的情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一种例外,默认的对象原型在原型链的顶端。更多关于原型链的将在后面介绍)

好吧,再绕回来,那什么又是对象呢?

在javascript中,一个对象就是任何无序键值对的集合,如果它不是一个主数据类型(undefined,null,boolean,number,or string),那它就是一个对象

你说每个对象都有一个原型,可是我当我写成({}).prototype 我得到了一个null,你说的不对吧?

忘记你已经学到的关于原型属性的一切,它可能就是你对原型困惑的根源所在。一个对象的真正原型是被对象内部的[[Prototype]]属性(property)所持有。ECMA引入了标准对象原型访问器Object.getPrototype(object),到目前为止只有Firefox和chrome实现了此访问器。除了IE,其他的浏览器支持非标准的访问器__proto__,如果这两者都不起作用的,我们需要从对象的构造函数中找到的它原型属性。下面的代码展示了获取对象原型的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = {};
 
 //Firefox 3.6 and Chrome 5
 
 Object.getPrototypeOf(a); //[object Object]  
 
 //Firefox 3.6, Chrome 5 and Safari 4
 
a.__proto__; //[object Object]  
 
 //all browsers
 
 a.constructor.prototype; //[object Object]

ok,一切都进行的很好,但是false明明是一个主数据类型,可是false.__proto__却返回了一个值

当你试图获取一个主数据类型的原型时,它被强制转化成了一个对象

1
2
3
//(works in IE too, but only by accident)
 
false.__proto__ === Boolean(false).__proto__; //true

我想在继承中使用原型,那我该怎么做?

如果仅仅只是因为一个实例而使用原型是没有多大意义的,这和直接添加属性到这个实例是一样的,假如我们已经创建了一个实例对象 ,我们想要继承一个已经存在的对象的功能比如说Array,我们可以像下面这样做( 在支持__proto__ 的浏览器中)

 

1
2
3
4
//unusual case and does not work in IE
var a = {};
a.__proto__ = Array.prototype;
a.length; //0

———————————————————————————————————–
 译者注:上面这个例子中,首先创建了一个对象a,然后通过a的原型来达到继承Array 这个已经存在的对象的功能
———————————————————————————————————–

原型真正魅力体现在多个实例共用一个通用原型的时候。原型对象(注:也就是某个对象的原型所引用的对象)的属性一旦定义,就可以被多个引用它的实例所继承(注:即这些实例对象的原型所指向的就是这个原型对象),这种操作在性能和维护方面其意义是不言自明的

这也是构造函数的存在的原因么?

是的。构造函数提供了一种方便的跨浏览器机制,这种机制允许在创建实例时为实例提供一个通用的原型

在你能够提供一个例子之前,我需要知道constructor.prototype 属性究竟是什么?

首先,javascript并没有在构造函数(constructor)和其他函数之间做区分,所以说每个函数都有一个原型属性。反过来,如果不是函数,将不会有这样一个属性。请看下面的代码

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//function will never be a constructor but it has a prototype property anyway
 
Math.max.prototype; //[object Object]
 
//function intended to be a constructor has a prototype too
 
var A = function(name) {
 
     this.name = name;
 
}
 
 A.prototype; //[object Object]   
 
 //Math is not a function so no prototype property
 
 Math.prototype; //null

现在我们可以下个定义了:函数A的原型属性(prototype property )是一个对象,当这个函数被用作构造函数来创建实例时,该函数的原型属性将被作为原型赋值给所有对象实例(注:即所有实例的原型引用的是函数的原型属性)
———————————————————————————————————-
译者注:以下的代码更详细的说明这一切

//创建一个函数b
var b = function(){ var one; }
//使用b创建一个对象实例c
var c = new b();
//查看b 和c的构造函数
b.constructor;  // function Function() { [native code]}
b.constructor==Function.constructor; //true
c.constructor; //实例c的构造函数 即 b function(){ var one; }
c.constructor==b //true

//b是一个函数,查看b的原型如下
b.constructor.prototype // function (){}
b.__proto__  //function (){}

//b是一个函数,由于javascript没有在构造函数constructor和函数function之间做区分,所以函数像constructor一样,
//
有一个原型属性,
这和函数的原型(b.__proto__ 或者b.construtor.prototype)是不一样的
b.prototype //[object Object]   函数b的原型属性

b.prototype==b.constructor.prototype //fasle
b.prototype==b.__proto__  //false
b.__proto__==b.constructor.prototype //true

//c是一个由b创建的对象实例,查看c的原型如下
c.constructor.prototype //[object Object] 这是对象的原型
c.__proto__ //[object Object] 这是对象的原型

c.constructor.prototype==b.constructor.prototype;  //false  c的原型和b的原型比较
c.constructor.prototype==b.prototype;  //true c的原型和b的原型属性比较

//为函数b的原型属性添加一个属性max
b.prototype.max = 3
//实例c也有了一个属性max
c.max  //3
上面的例子中,对象实例c的原型和函数的b的原型属性是一样的,如果改变b的原型属性,则对象实例c
的原型也会改变

———————————————————————————————————-

理解一个函数的原型属性(function’s prototype property )其实和实际的原型(prototype)没有关系对我们来说至关重要

1
2
3
4
5
6
7
8
9
10
11
//(example fails in IE)
 
 var A = function(name) {
 
  this.name = name;
 
}
 
A.prototype == A.__proto__; //false
 
 A.__proto__ == Function.prototype; //true - A's prototype is set to its constructor's prototype property

给个例子撒

你可能曾经上百次的像这样使用javascript,现在当你再次看到这样的代码的时候,你或许会有不同的理解。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Constructor. <em>this</em> is returned as new object and its internal [[prototype]] property will be set to the constructor's default prototype property
var Circle = function(radius) {
    this.radius = radius;
    //next line is implicit, added for illustration only
    //this.__proto__ = Circle.prototype;
 }    
//augment Circle's default prototype property thereby augmenting the prototype of each generated instance
Circle.prototype.area = function() {
    return Math.PI*this.radius*this.radius;
 }   
 //create two instances of a circle and make each leverage the common prototype
var a = new Circle(3), b = new Circle(4);
 a.area().toFixed(2); //28.27
 b.area().toFixed(2); //50.27

棒极了。如果我更改了构造函数的原型,是否意味着已经存在的该构造函数的实例将获得构造函数的最新版本?

不一定。如果修改的是原型属性,那么这样的改变将会发生。因为在a实际被创建之后,a.__proto__是一个对A.prototype 的一个引用,。

1
2
3
4
5
6
7
var A = function(name) {
    this.name = name;
 }   
var a = new A('alpha');
a.name; //'alpha'   
 A.prototype.x = 23;   
a.x; //23

——————————————————————————————————
译者注:这个和上例中的一样,实例对象a的原型(a.__proto__)是对函数A的原型属性(A.prototype)的引用,所以如果修改的是A的原型属性,

改变将影响由A创建的对象实例a 在下面的例子中,但是对函数A的原型进行了修改,但是并没有反应到A所创建的实例a中

var A = function(name)
{
this.name = name;
}
var a = new A(‘alpha’);
a.name; //’alpha’

A.__proto__.max = 19880716;

a.max   //undefined

——————————————————————————————————

但是如果我现在替换A的原型属性为一个新的对象,实例对象的原型a.__proto__却仍然引用着原来它被创建时A的原型属性

1
2
3
4
5
6
7
var A = function(name) {
    this.name = name;
}  
 var a = new A('alpha');
a.name; //'alpha'   
 A.prototype = {x:23};    
 a.x; //null

——————————————————————————————————————
译者注:即如果在实例被创建之后,改变了函数的原型属性所指向的对象,也就是改变了创建实例时实例原型所指向的对象

但是这并不会影响已经创建的实例的原型。
——————————————————————————————————————-

一个默认的原型是什么样子的?

1
2
3
4
var A = function() {};
 A.prototype.constructor == A; //true
 var a = new A();
 a.constructor == A; //true (a's constructor property inherited from it's prototype)

instance of 和原型有什么关系

如果a的原型属于A的原型链,表达式 a instance of A 值为true。这意味着 我们可以对instance of 耍个诡计让它不在起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var A = function() {}   
 
 var a = new A();
 
 a.__proto__ == A.prototype; //true - so instanceof A will return true
 
 a instanceof A; //true;   
 
//mess around with a's prototype
 
 a.__proto__ = Function.prototype;
 
 //a's prototype no longer in same prototype chain as A's prototype property
 
 a instanceof A; //false

还能使用原型做些什么呢?

记住我曾经所提到过的每个构造函数都有一个原型属性,它用来为每一个它所创建的实例提供原型。这同样也适用原生态的构造函数Function,String等,扩展这个属性,我们可以达到扩展指定构造函数的所有实例
我曾经在之前的很多文章中使用过这个技巧来演示函数的拓展。在tracer utility 这篇文章中所有的string实例都实现了times这个方法,对字符串本身进行指定数目的复制

1
2
3
4
5
6
7
String.prototype.times = function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
 }
 
"hello!".times(3); //"hello!hello!hello!";
 
"please...".times(6);//"please...please...please...please...please...please..."

告诉我继承是怎样通过原型来工作的。什么是原型链?

因为每个对象和原型都有一个原型(注:原型也是一个对象),对象的原型指向对象的父,而父的原型又指向父的父,我们把这种通过原型层层连接起来的关系撑为原型链。这条链的末端一般总是默认的对象原型。

1
2
3
4
5
6
7
a.__proto__ = b;
 
 b.__proto__ = c;
 
 c.__proto__ = {}; //default object
 
{}.__proto__.__proto__; //null

原型的继承机制是发生在内部且是隐式的.当想要获得一个对象a的属性foo的值,javascript会在原型链中查找foo的存在,如果找到则返回foo的值,否则undefined被返回。

赋值呢?

原型的继承 is not a player 当属性值被设置成a.foo=’bar’是直接给a的属性foo设置了一个值bar。为了把一个属性添加到原型中,你需要直接指定该原型。

以上就是这篇文章所要讲述的。我个人感觉自己对原型概念的理解还是比较深刻的,但我的观点并不能代表一切。如有纰漏和异议,敬请告知。

我在哪里还能获得关于原型更多的信息?

我推荐这篇由Dmitry A. Soshnikov所著的文章,非常优秀。

 

分享到:
评论

相关推荐

    深入理解javascript原型和闭包

    深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系

    理解Javascript原型继承原理

    ### 理解Javascript原型继承原理 #### 一、引言 在JavaScript中,原型继承是一种非常核心且独特的机制,它使得对象能够继承其他对象的属性和方法。本文旨在深入探讨这一机制,并通过具体的示例代码帮助读者更好地...

    深入理解javascript原型和闭包.pdf

    JavaScript原型和闭包是这门语言中两个比较难以理解且与其他面向对象语言区别较大的概念。理解这两个概念,不仅能让我们更深层次地理解JavaScript,而且有助于我们了解编程语言的设计思路,拓宽我们的视野。 首先,...

    深入理解javascript原型和闭包1

    JavaScript是一种动态类型的...通过这些深入的讲解,你应该能更好地理解JavaScript中对象、原型、函数和闭包的工作原理,为你的JavaScript开发打下坚实的基础。在实际编程中,灵活运用这些知识可以解决许多复杂的问题。

    深入理解javascript原型链和继承

    在深入理解JavaScript原型链和继承的概念之前,首先要了解JavaScript是一种基于对象的语言,而非传统的面向对象语言。它没有类的概念,函数可以被视为构造器,而对象则是通过构造函数、原型对象和实例之间的特殊关系...

    Javascript原型继承

    理解JavaScript原型继承是深入学习JavaScript的关键,它在实际开发中有着广泛的应用,尤其是在构建复杂的数据结构和实现模块化代码时。熟练掌握这一概念,将有助于提升JavaScript编程的灵活性和效率。

    深入浅出理解javaScript原型链

    JavaScript的原型链是其面向对象特性的重要组成部分,它允许对象之间共享属性和方法,从而实现继承。本文将深入探讨JavaScript的原型链,包括显式和隐式原型链,以及它们在实际编程中的应用。 首先,我们需要了解两...

    深入理解JavaScript系列

    深入理解JavaScript系列(5):强大的原型和原型链 深入理解JavaScript系列(6):S.O.L.I.D五大原则之单一职责SRP 深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP 深入理解JavaScript系列(8):...

    学习javascript面向对象 理解javascript原型和原型链

    在深入理解JavaScript面向对象编程时,了解其原型和原型链的概念至关重要。本文将详细解释这些概念以及它们如何工作,帮助初学者构建扎实的基础。 首先,原型链是JavaScript中实现继承的一种机制。在JavaScript中,...

    JavaScript原型链

    JavaScript原型链是JavaScript语言中的一个核心特性,它关乎对象之间的继承关系。在JavaScript中,一切皆为对象,而原型链则是实现对象间属性和方法共享的一种机制。理解原型链对于深入学习JavaScript至关重要。 ...

    理解JavaScript原型链

    JavaScript原型链是这门语言的核心概念之一,它关系到对象属性的继承机制。在JavaScript中,所有对象都有一个原型,原型也是一个对象,从而形成了一条原型链。每个对象通过内部属性[[Prototype]](ES6后建议使用...

    基于js原型链的小游戏

    JavaScript是Web开发中不可或缺的一部分,尤其在前端领域,它的强大在于动态性和灵活性。在这个"基于js原型链的小游戏"中,我们...这不仅是一个理解JavaScript原型链的好例子,也是实践前端开发技能的绝佳实践项目。

    【JavaScript源代码】五句话帮你轻松搞定js原型链.docx

    以下是五句话帮助你理解JavaScript原型链: 1. `Function`和`Object`都是JavaScript的构造函数。`Function`用于创建其他函数,而`Object`则用于创建所有类型的实例对象。 2. 所有的构造函数都是通过`Function`构造...

    深入理解JavaScript系列(.chm)

    深入理解JavaScript系列(5):强大的原型和原型链 深入理解JavaScript系列(6):S O L I D五大原则之单一职责SRP 深入理解JavaScript系列(7):S O L I D五大原则之开闭原则OCP 深入理解JavaScript系列(8):...

Global site tag (gtag.js) - Google Analytics