Javascript 之 Prototype

1. Prototype 属性

JavaScript 中的 function 本质就是一个 object 对象,它本身包含了一些方法(apply(),call())和一些属性(length, constructor),这其中还包含一个名为 prototype 的属性。

当你定义了一个 function 后,你就能访问到这个 prototype 属性,它的初始值是一个”空”的 object 对象:

function foo() { ... }

typeof foo.prototype;
// "object"

你可以随意设定这个对象,给它加上属性或者方法,但是这不会对这个 function 本身造成任何影响,除非你把它最为构造函数来使用。

使用 prototype 来添加方法和属性

当使用 new 来实例化一个对象时,在 function 内可以通过 this 关键字来对这个对象进行成员追加:

function Gadget(name, color) {
  this.name = name;
  this.color = color;
  this.whatAreYou = function () {
    return ‘I am a ‘ + this.color + ‘ ‘ + this.name;
  };
}

另外我们也可以在 functionprototype 属性上进行相同的处理:

Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
Gadget.prototype.getInfo = function () {
  return ‘Rating: ‘ + this.rating + ‘, price: ‘ + this.price;
};

prototype 对象甚至可以被整个替换:

Gadget.prototype = {
  price: 100,
  rating: ... /* 其他成员... */
};

2. 使用 prototype 的方法和属性

prototype 上添加的方法和属性都能够在实例化后的对象上调用:

var newtoy = new Gadget(‘webcam‘, ‘black‘);
newtoy.name;
// "webcam"

newtoy.color;
// "black"

newtoy.whatAreYou();
// "I am a black webcam"

newtoy.price;
// 100

newtoy.rating;
// 3

newtoy.getInfo();
// "Rating: 3, price: 100"

object 对象在 Javascript 中都是以引用方式传递的,所以 prototype 并非在每个实例对象中保存一份。当你改变 prototype 时,所有的实例对象都能立即“察觉”这些变动。假设我们再增加一个新的方法:

Gadget.prototype.get = function (what) {
  return this[what];
};

前文的 newtoy 虽然在此前已经被实例化,但他仍然能使用到这个新方法:

newtoy.get(‘price‘);
// 100
newtoy.get(‘color‘);
// "black"

自有属性 与 prototype 属性

先前我们定义过一个 getInfo() 方法,它修改为以下的方式后也能获得同样的输出结果:

Gadget.prototype.getInfo = function () {
  return ‘Rating: ‘ + Gadget.prototype.rating + ‘, price: ‘ + Gadget.prototype.price;
};

这个原由需要从头说起,先来看 newtoy 对象是怎样实例化的:

var newtoy = new Gadget(‘webcam‘, ‘black‘);

当你访问 newtoy 的某个属性的时候(这里假设是 newtoy.name),JavaScript 引擎会搜索它的所有名为 name 的属性,如果发现了就返回它的值:

newtoy.name;
// "webcam"

当你访问 rating 属性时情况变了,JavaScript 引擎在 newtoy 上找不到名为 rating 的属性,然后他就会到 newtoy 的构造函数(Gadget) 的 prototype 属性上继续查找:

newtoy.rating;
// 3

以下的代码验证了这一点:

newtoy.constructor === Gadget;
// true
newtoy.constructor.prototype.rating;
// 3

我们知道每个 object 对象都有一个构造函数,那么作为 object 对象的 prototype 也必然存在一个构造函数。这就形成了一个 prototype chain (prototype 链),这个链的最上层就是内置的 Object() 对象。 要验证这一点很容易,newtoy 没有 toString() 方法,它的 prototype 上也没有,但是你却能调用 newtoy.toString() ,因为 object 对象有这个方法:

newtoy.toString();
// "[object Object]"

自有属性复写 prototype 属性

当自有属性与 prototype 属性重名时,自有属性优先:

function Gadget(name) {
  this.name = name;
}
Gadget.prototype.name = ‘mirror‘;
var toy = new Gadget(‘camera‘);
toy.name;
// "camera"

使用 hasOwnProperty() 可以知道某个属性知否是自有属性:

toy.hasOwnProperty(‘name‘);
// true

我们把自有属性 name 删了再瞧瞧什么情况:

delete toy.name;
// true
toy.name;
// "mirror"
toy.hasOwnProperty(‘name‘);
// false

Enumerating properties

使用 for-in 语句能够遍历出一个对象的所有属性:

虽然 for-in 也适用于数组,但建议遍历数组时采用 for,遍历对象时采用 for-in。

var params = {
  productid: 666,
  section: ‘products‘
};
var url = ‘http://example.org/page.php?‘,
i,
query = [];
for (i in params) {
  query.push(i + ‘=‘ + params[i]);
}
url += query.join(‘&‘);

以上代码输出:

http://example.org/page.php?productid=666&section=products

以下几点需要注意:

  • 并非所有属性能够在 for-in 中遍历到,譬如 constructor 属性等等。所有能遍历到的属性称为 enumerable。你可以用 propertyIsEnumerable() 方法来区分,在 ES5 中你甚至可以自定义哪些属性是 enumerable
  • Prototype 链上的 enumerable 属性也会被遍历到。
  • 由 Prototype 链上而来的 enumerable 属性,被传入 propertyIsEnumerable() 方法时总返回 false

通过实例来看一下:

function Gadget(name, color) {
  this.name = name;
  this.color = color;
  this.getName = function () {
    return this.name;
  };
}
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;

var newtoy = new Gadget(‘webcam‘, ‘black‘);

for (var prop in newtoy) {
  console.log(prop + ‘ = ‘ + newtoy[prop]);
}

// 输出
name = webcam
color = black
getName = function () {
  return this.name;
}
price = 100
rating = 3

再试一下 propertyIsEnumerable() 方法:

// 自有属性
newtoy.propertyIsEnumerable(‘name‘);
// true

// 内置属性
newtoy.propertyIsEnumerable(‘constructor‘);
// false

// prototype 链上的属性
newtoy.propertyIsEnumerable(‘price‘);
// false

// 改变调用对象后
newtoy.constructor.prototype.propertyIsEnumerable(‘price‘);
// true

isPrototypeOf()

isPrototypeOf() 可以用来确认某个对象是否另一个对象的 prototype

var monkey = {
  hair: true,
  feeds: ‘bananas‘,
  breathes: ‘air‘
};

function Human(name) {
  this.name = name;
}
Human.prototype = monkey;

var george = new Human(‘George‘);
monkey.isPrototypeOf(george);
// true

那么当你对 prototype 一无所知时,怎么办呢?对于支持 ES5 的环境,你可以使用 getPrototypeOf() 方法。

> Object.getPrototypeOf(george).feeds;
"bananas"
> Object.getPrototypeOf(george) === monkey;
true

如果遇到不支持 ES5 的环境,你可以使用 __proto__ 这个特殊的属性。

__proto__ 连接

前文提到当你访问一个非自有属性时, 引擎通过 prototype 继续查找:

var monkey = {
  feeds: ‘bananas‘,
  breathes: ‘air‘
};
function Human() {}
Human.prototype = monkey;

var developer = new Human();
developer.feeds = ‘pizza‘;
developer.hacks = ‘JavaScript‘;

developer.feeds;
// "pizza"

developer.breathes;
// "air"

在现今许多 JavaScript 环境中,都是通过一个名为 __proto__ 的属性来实现的:

developer.__proto__ === monkey;
// true

需要注意的是 __proto__ 与 prototype 是不同的,__proto__ 是一个实例对象的属性,而 prototype 是一个构造函数的属性。

typeof developer.__proto__;
// "object"
typeof developer.prototype;
// "undefined"
typeof developer.constructor.prototype;
// "object"

你应当仅在调试环境中使用这个 __proto__ 属性来获取信息


3. 内置对象的扩展

内置的构造函数(诸如:Array,String 以及 Object)都可以通过 prototype 来进行扩展。见示例:

Array.prototype.inArray = function (needle) {
  for (var i = 0, len = this.length; i < len; i++) {
    if (this[i] === needle) {
      return true;
    }
  }
  return false;
};

var colors = [‘red‘, ‘green‘, ‘blue‘];
colors.inArray(‘red‘);
// true
colors.inArray(‘yellow‘);
// false

上面的方法与 apply() 等函数的灵活结合可以写出非常高效的代码。譬如我们为 String 增加一个字符串反转的函数:

String.prototype.reverse = function () {
  return Array.prototype.reverse.apply(this.split(‘‘)).join(‘‘);
};

代码是不是简洁得出乎你的意料?

内置对象的扩展 – 注意点

扩展内置对象是一个强大的功能,但不应当过度使用,因为对这些内置对象的修改会对使用者和维护者造成困惑。

另外随着各个浏览器的升级,JavaScript 环境也会对这些内置对象进行扩展,这就可能与你的扩展造成冲突。

目前有一些类库致力于在不同的 JavaScript 环境中提供一致的调用接口,这些类库被称之为 shims 或 polyfills。

自行对内置对象的扩展应当谨慎,以下提供一种比较保险的做法:

if (typeof String.prototype.trim !== ‘function‘) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g,‘‘);
  };
}

" hello ".trim();
// "hello"

Prototype 陷阱

当你操作 prototype 时应当牢记以下两点:

  • 如果你替换了整个 prototype 对象,那么你也打断了 prototype 链
  • prototype.constructor 是不可靠的

非常抽象难懂对不对,看个例子你就明白了:

function Dog() {
  this.tail = true;
}
var benji = new Dog();

此时如果你扩展 Dog(),prototype 链能保证已经实例化的 benji 能够使用到新的扩展:

Dog.prototype.say = function () {
  return ‘Woof!‘;
};

benji.say();
// "Woof!"

benji.constructor === Dog;
// true

接着我们整个替换掉 Dog() 的 prototype 会怎样呢?

Dog.prototype = {
  paws: 4,
  hair: true
};

typeof benji.paws;
// "undefined"
benji.say();
// "Woof!"

typeof benji.__proto__.paws;
// "undefined"
typeof benji.__proto__.say;
// "function"

早先实例化的 benji 对象无法访问到扩展成员(paws, hair),但它仍然能调用替换前的成员。

那么对于新的实例对象是什么情况呢?

var lucy = new Dog();
lucy.say();
// TypeError: lucy.say is not a function
lucy.paws;
// 4
typeof lucy.__proto__.say;
// "undefined"
typeof lucy.__proto__.paws;
// "number"

这个新的实例对象显然可以调用到最新的扩展方法,如果你检查一下 constructor 属性你会发现返回值为 Object(),而不是预期的 Dog()。

lucy.constructor;
// function Object() { [native code] }
benji.constructor;
// function Dog() {
//   this.tail = true;
// }

你可以在整个替换 prototype 属性后强行改正 constructor 属性来避免发生这样的困扰。

function Dog() { ... }
Dog.prototype = { ... };
new Dog().constructor === Dog;
// false

// 强行改正 constructor
Dog.prototype.constructor = Dog;
new Dog().constructor === Dog;
// true

强烈建议每次整个替换 prototype 属性后强行设置一下 constructor 属性。


4. 总结

  • 所有 function 都有一个名为 prototype 的属性,初始化时他只是一个空对象。
  • 你可以在这个 prototype 对象上增加属性或方法,也可以整个替换掉它。
  • 当你使用一个 function 来实例化一个对象时,这个对象会保存一个指向 functionprototype 属性的链接。
  • 对象的自有属性优先于 prototype 属性。
  • hasOwnProperty() 可以用来区分自有属性和 prototype 属性。
  • JavaScript 的查找本质是在 prototype 链上查找。
  • 内置的构造函数可以扩展,但扩展是需要注意不同的 JavaScript 环境,并在扩展前做好确认检查。
时间: 2024-11-03 18:31:19

Javascript 之 Prototype的相关文章

在 JavaScript 中 prototype 和 __proto__ 有什么区别

本文主要讲三个 问题 prototype 和 proto function 和 object new 到底发生了什么 prototype 和 proto 首先我们说下在 JS 中,常常让我们感到困惑的地方,就是 prototype 和 __proto__ 到底是干嘛的 1. __proto__ 就是 Javascript中 所谓的原型 (这里,我们还是拿具体的例子来说明吧) function A (name) { // 这里是一个构造函数 thia.name = name } var Aobj

javascript继承—prototype属性介绍(2)

js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向自身.这会形成一个非常有意思的链式结构.举例如下: function Person(){ this.name =12; } console.log(Person.prototype); console.log(Person.prototype.constructor);//输出Person,指向自身

javascript继承—prototype最优两种继承(空函数和循环拷贝)(3) - jssl915

一.利用空函数实现继承 参考了文章javascript继承-prototype属性介绍(2) 中叶小钗的评论指点,对这篇文章中的方案二利用一个空函数进行修改,可以解决创建子类对象时,父类实例化的过程中特权属性和特权方法,私有属性,私有方法的空耗资源问题. function Person(name,age){ this.name = name; this.age = age; } Person.prototype = { constructor:Person, sayHi:function(){

为什么要设置Javascript对象prototype的constructor

最近读了一篇关于Javascript面向对象编程的文章,里面介绍了Javascript中的类,对象,属性,方法,构造函数,继承,封装,抽象和多态性.读完之后感觉受益匪浅,对Javascript有了进一步的认识.文章的地址在这里. 在讲到继承的时候,文章里面用了如下的例子 // define the Person Class function Person() {} Person.prototype.walk = function(){ alert ('I am walking!'); }; Pe

javascript的prototype经典使用场景

prototype的经典使用场景就是为对象增加属性和方法,如给自定义的Man对象增加个姓名属性和语言方法: function man() { this.age = "22"; } var tom = new man(); man.prototype.name = "tom"; man.prototype.say = function () { alert("english"); }; alert(tom.name); tom.say(); 结果:

关于Javascript的prototype

Javascript中的每个对象(object)都会有 prototype .试一下: var Richard = new Object(); alert(typeof(Richard.prototype)); 结果令人郁闷,浏览器弹出来的是 undefined-- 到底是怎么回事呢? 再看一个例子: function Richard(){} alert(typeof(Richard.prototype)); 上面的例子似乎说明,只有 function 对象才有 prototype, 而一般的

javascript Date.prototype

Date.prototype.format = function(format){ var o = { "M+" : this.getMonth()+1, //month "d+" : this.getDate(), //day "h+" : this.getHours(), //hour "m+" : this.getMinutes(), //minute "s+" : this.getSeconds()

理清javascript中prototype、__proto__、Object、Function的关系,更好地理解原型继承

本文参考了http://www.blogjava.net/heavensay/archive/2013/10/20/405440.html这篇文章,对其内容作了个简单总结,形成了几条简单的结论,让读者更容易记住prototype.__proto__.Object.Function之间的关系. 结论1:Object.prototype只是一个普通对象,它是js原型链的最顶端. (typeof Object.prototype) === object;//true Object.prototype.

javascript原型Prototype

在javaScript创建对象一文中提到过:用构造函数创建对象存在一个问题即同一构造函数的不同实例的相同方法是不一样的,所以我们用原型把构造函数中公共的属性和方法提取出来进行封装,达到让所有实例共享的目的. 接下来进一步介绍javaScript原型. 一.javaScript原型机制 1.函数与原型的关系 js中创建一个函数,就会自动创建一个prototype属性,这个属性指向函数的原型对象,并且原型对象会自动获得一个constructor(构造函数)属性,指向该函数. 举例:以前面的原型模式创

javascript 之 prototype继承机制

理解Javascript语言的继承机制 javascript没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承. 1.引言 1994年,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动.工程师Brendan Eich 负责开发这种新语言.他觉得,没必要设计得很复杂,这种语言只要能够完