您的位置:首页 > Web前端 > JavaScript

javascript 原型、原型链、对象复制等原理和示例分析(下)

2015-05-09 13:23 741 查看
原型

原型是 JavaScript 面向对象特性中重要的概念,也是大家太熟悉的概念。因为在绝大多

数的面向对象语言中,对象是基于类的(例如 Java 和 C++ ) ,对象是类实例化的结果。而在

JavaScript 语言中,没有类的概念

① ,对象由对象实例化。打个比方来说,基于类的语言中类

就像一个模具,对象由这个模具浇注产生,而基于原型的语言中,原型就好像是一件艺术品

的原件,我们通过一台 100% 精确的机器把这个原件复制出很多份。

前面小节的例子中都没有涉及原型,仅仅通过构造函数和 new 语句生成类,让我们看

看如何使用原型和构造函数共同生成对象。

function Person() {}

Person.prototype.name = ‘BYVoid’;

Person.prototype.showName = function() {

console.log(this.name);

};

var person = new Person();

person.showName();

上面这段代码使用了原型而不是构造函数初始化对象。 这样做与直接在构造函数内定义

属性有什么不同呢?

**构造函数内定义的属性继承方式与原型不同, 子对象需要显式调用父对象才能继承构

造函数内定义的属性。

构造函数内定义的任何属性, 包括函数在内都会被重复创建, 同一个构造函数产生的

两个对象不共享实例。**

构造函数内定义的函数有运行时闭包的开销, 因为构造函数内的局部变量对其中定义

的函数来说也是可见的。

下面这段代码可以验证以上问题:

function Foo() {

var innerVar = ‘hello’;

this.prop1 = ‘BYVoid’;

this.func1 = function() {

innerVar = ”;

};

}

Foo.prototype.prop2 = ‘Carbo’;

Foo.prototype.func2 = function() {

console.log(this.prop2);

};

var foo1 = new Foo();

var foo2 = new Foo();

console.log(foo1.func1 == foo2.func1); // 输出 false

console.log(foo1.func2 == foo2.func2); // 输出 true

尽管如此,并不是说在构造函数内创建属性不好,而是两者各有适合的范围。那么我们

什么时候使用原型,什么时候使用构造函数内定义来创建属性呢?

**除非必须用构造函数闭包,否则尽量用原型定义成员函数,因为这样可以减少开销。

尽量在构造函数内定义一般成员, 尤其是对象或数组, 因为用原型定义的成员是多个

实例共享的。**

接下来,我们介绍一下JavaScript中的原型链机制。

原型链

JavaScript 中有两个特殊的对象: Object 与 Function ,它们都是构造函数,用于生

成对象。 Object.prototype 是所有对象的祖先, Function.prototype 是所有函数的原

型,包括构造函数。我把 JavaScript 中的对象分为三类,

一类是用户创建的对象,

一类是构造函数对象,

一类是原型对象。

用户创建的对象,即一般意义上用 new 语句显式构造的对象。

构造函数对象指的是普通的构造函数,即通过 new 调用生成普通对象的函数。

原型对象特指构造函数 prototype 属性指向的对象。

这三类对象中每一类都有一个 proto 属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到 Object.prototype 。

构造函数对象有 prototype 属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的 proto 属性将会指向构造函数的 prototype 属性。

原型对象有 constructor属性,指向它对应的构造函数。让我们通过下面这个例子来理解原型:

function Foo() {}

Object.prototype.name = ‘My Object’;

Foo.prototype.name = ‘Bar’;

var obj = new Object();

var foo = new Foo();

console.log(obj.name); // 输出 My Object

console.log(foo.name); // 输出 Bar

console.log(foo.proto.name); // 输出 Bar

console.log(foo.proto.proto.name); // 输出 My Object

console.log(foo.proto.constructor.prototype.name); // 输出 Bar

我们定义了一个叫做 Foo () 的构造函数,生成了对象 foo 。同时我们还分别给 Object和 Foo 生成原型对象。

下图解析了它们之间错综复杂的关系。



对象的复制

JavaScript 和 Java 一样都没有像C语言中一样的指针,所有对象类型的变量都是指向对

象的引用,两个变量之间赋值传递一个对象并不会对这个对象进行复制,而只是传递引用。

有些时候我们需要完整地复制一个对象,这该如何做呢? Java 语言中有 clone 方法可以实

现对象复制,但 JavaScript 中没有这样的函数。因此我们需要手动实现这样一个函数,一个

简单的做法是复制对象的所有属性:

Object.prototype.clone = function() {

var newObj = {};

for (var i in this) {

newObj[i] = this[i];

}

return newObj;

}

var obj = {

name: ‘byvoid’,

likes: [‘node’]

};

var newObj = obj.clone();

obj.likes.push(‘python’);

console.log(obj.likes); // 输出 [ ‘node’, ‘python’ ]

console.log(newObj.likes); // 输出 [ ‘node’, ‘python’ ]

上面的代码是一个对象浅拷贝(shallow copy)的实现,即只复制基本类型的属性,而

共享对象类型的属性。 浅拷贝的问题是两个对象共享对象类型的属性, 例如上例中 likes 属

性指向的是同一个数组。

实现一个完全的复制,或深拷贝(deep copy)并不是一件容易的事,因为除了基本数据

类型,还有多种不同的对象,对象内部还有复杂的结构,因此需要用递归的方式来实现:

Object.prototype.clone = function() {

var newObj = {};

for (var i in this) {

if (typeof(this[i]) == ‘object’ || typeof(this[i]) == ‘function’) {

newObj[i] = this[i].clone();

} else {

newObj[i] = this[i];

}

}

return newObj;

};

Array.prototype.clone = function() {

var newArray = [];

for (var i = 0; i < this.length; i++) {

if (typeof(this[i]) == ‘object’ || typeof(this[i]) == ‘function’) {

newArray[i] = this[i].clone();

} else {

newArray[i] = this[i];

}

}

return newArray;

};

Function.prototype.clone = function() {

var that = this;

var newFunc = function() {

return that.apply(this, arguments);

};

for (var i in this) {

newFunc[i] = this[i];

}

return newFunc;

};

var obj = {

name: ‘byvoid’,

likes: [‘node’],

display: function() {

console.log(this.name);

},

};

var newObj = obj.clone();

newObj.likes.push(‘python’);

console.log(obj.likes); // 输出 [ ‘node’ ]

console.log(newObj.likes); // 输出 [ ‘node’, ‘python’ ]

console.log(newObj.display == obj.display); // 输出 false

上面这个实现看起来很完美,它不仅递归地复制了对象复杂的结构,还实现了函数的深

拷贝。这个方法在大多数情况下都很好用,但有一种情况它却无能为力,例如下面的代码:

var obj1 = {

ref: null

};

var obj2 = {

ref: obj1

};

obj1.ref = obj2;

这段代码的逻辑非常简单,就是两个相互引用的对象。当我们试图使用深拷贝来复制

obj1 和 obj2 中的任何一个时,问题就出现了。因为深拷贝的做法是遇到对象就进行递归

复制,那么结果只能无限循环下去。对于这种情况,简单的递归已经无法解决,必须设计一

套**图论算法, 分析对象之间的依赖关系, 建立一个拓扑结构图, 然后分别依次复制每个顶点,

并重新构建它们之间的依赖关系**。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: