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

简介: 原型 原型是 JavaScript 面向对象特性中重要的概念,也是大家太熟悉的概念。因为在绝大多 数的面向对象语言中,对象是基于类的(例如 Java 和 C++ ) ,对象是类实例化的结果。而在 JavaScript 语言中,没有类的概念 ① ,对象由对象实例化。打个比方来说,基于类的语言中类 就像一个模具,对象由这个模具浇注产生,而基于原型的语言中,原型就好像是一

原型
原型是 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 中的任何一个时,问题就出现了。因为深拷贝的做法是遇到对象就进行递归
复制,那么结果只能无限循环下去。对于这种情况,简单的递归已经无法解决,必须设计一
套**图论算法, 分析对象之间的依赖关系, 建立一个拓扑结构图, 然后分别依次复制每个顶点,
并重新构建它们之间的依赖关系**。

目录
相关文章
|
4月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
4月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
4月前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
4月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
46 1
JavaScript中对象的数据拷贝
|
4月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
55 1
JavaScript中的原型 保姆级文章一文搞懂
|
4月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
126 17
|
4月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
139 1
|
4月前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
4月前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
4月前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。

热门文章

最新文章