javascript成神之路(2):深入理解原型以及原型链的重要性

原型

一、什么是原型

每个函数对象都有一个prototype属性,这个属性这个属性是一个指针,指向一个对象。当函数作为构造函数使用时这个对象会成为调用该构造函数而创建的实例的原型,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。是不是感觉一脸懵逼呢?我们先看看下面一个例子: javascript成神之路(2):深入理解原型以及原型链的重要性

function Sanpang(val) { this.valueA = val; } Sanpang.prototype.sayHello = function () { console.log(this.valueA); } var f1 = new Sanpang(1); var f2 = new SanpangF(2); // 由F函数生成的实例都会包含sayA方法 f1.sayHello(); f2.sayHello();

就是说,构造函数生成的实例会有一组共享的属性和方法,这些属性和方法在构造函数的原型对象里。看到这里大家应该稍微有点眉目了吧。

二、原型的使用方式

1.在赋值原型prototype的时候使用function立即执行的表达式来赋值,示例如下:

A.prototype = function () { } ();

这种方法的好处就是可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果。

A.prototype = function () { addF = function (x, y) { return x + y; }, subtractF = function (x, y) { return x - y; } return { add: addF, subtract: subtractF } } ();

调用的时候我们用new A.add(1,2),这样我们就计算出结果了。

2.分开设置每个原型的属性,示例如下:

var B = function () { this.decimalDigits = 11; }; B.prototype.add = function (x, y) { return x + y; }; B.prototype.subtract = function (x, y) { return x - y; };

第一种方式就是一次性设置了原型对象,所以有一定的弊端。这样我们就声明了一个B对象,构造函数里会初始化一个小数位数的属性decimalDigits,然后通过原型属性设置2个function,分别是add(x,y)和subtract(x,y),当然你也可以使用前面提到的2种方式的任何一种,我们的主要目的是看如何将B对象设置到真正的C的原型上。

var B = function() { this.decimalDigits = 2; }; B.prototype = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } };

当上B创建成功以后我们开始设置C

var C = function () { this.tax = 5; //每一个实例都有这个tax属性 }; C.prototype = new B();

这样的话C的原型是指向到B的一个实例上,目的是让C集成它的add(x,y)和subtract(x,y)这2个function,由于它的原型是B的一个实例,所以不管你创建多少个C对象实例,他们的原型指向的都是同一个实例。这样我们就可以运行下面的代码:

var c = new C(); alert(c.add(1, 1)); //B 里声明的decimalDigits属性,在 C里是可以访问到的 alert(c.decimalDigits); //同样是可以访问到的

如果我不想让C访问B的构造函数里声明的属性值,那该杂么做?

var C = function () { this.tax= 5; }; C.prototype = B.prototype;

这样通过将B的原型赋给C的原型,这样你在C的实例上就访问不到那个decimalDigits值了,如果你访问下面的代码将会报错。

var c = new C(); alert(c.add(1, 1)); alert(c.decimalDigits);

3.重写原型:

当我们使用第三方javascript类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下:

覆盖前面C的add() function C.prototype.add = function (x, y) { return x + y + this.tax; }; var c = new C(); alert(c.add(2, 2));

我们现在调用的结果就比原来多出了一个tax的值,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。

原型链

原型链,是JS实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。示例如下:

function F() { this.value = 1; } F.prototype = { method: function() {} }; function B() {} B.prototype = new F(); B.prototype.foo = 'Hello World'; // 修正B.prototype.constructor为B本身 B.prototype.constructor = B; var test = new B() // 创建B的一个新实例 // 原型链 t => [B的实例] B.prototype [F的实例] { foo: 'Hello World' } F.prototype {method: ...}; Object.prototype {toString:等等};

以上的例子,t 对象从 B.prototype 和 F.prototype 继承下来;因此,它能访问 F 的原型方法 method。同时,它也能够访问那个定义在原型上的 F 实例属性 value。需要注意的是 new B() 不会创造出一个新的 F 实例,而是重复使用它原型上的那个实例;因此,所有的 B 实例都会共享相同的 value 属性。

总结

  • 原型和原型链是 JS 实现继承的一种模型。
  • 原型链是靠 _proto _ 形成的,而不是 prototype。
  • 所有的原型对象都有 constructor 属性,该属性对应创建所有指向该原型的实例构造函数。
  • 函数对象和原型对象通过 prototype 和 constructor 属性进行相互关联。

原文发布时间:2018年01月07日

作者:技术金三胖

本文来源:开源中国  如需转载请联系原作者

上一篇:提高前端开发质量和效率的脚手架和工具套件 - Uix Kit


下一篇:基于Vue实现后台系统权限控制