js继承与原型链

对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来说,JavaScript 的语法是比较怪异的,这是由于 JavaScript 是一门动态语言,而且它没有类的概念(虽然 class 是个保留字,不能作为变量名来使用)。

继承方面,JavaScript 中的每个对象都有一个内部私有的链接指向另一个对象,这个对象就是原对象的原型。这个原型对象也有自己的原型,直到对象的原型为 null 为止(也就是没有原型)。这种一级一级的链结构就称为原型链

虽然这通常会被称作 JavaScript 的弱点之一,实际上这种原型继承的模型要比经典的继承模型还要强大。虽然在原型模型上构建一个经典模型是相当琐碎的,但如果采取其他方式实现则会更加困难。

基于原型链的继承

继承属性

JavaScript 对象有两种不同的属性,一种是对象自身的属性,另外一种是继承于原型链上的属性。下面的代码则演示了当访问一个对象的属性时发生的行为:

// 假定我们有个对象o,并且o所在的原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> null
// ‘a‘和‘b‘是o自身的属性.

// 该例中,用"对象.[[Prototype]]"来表示这个对象的原型.
// 这只是一个纯粹的符号表示(ECMAScript标准中也这样使用),不能在实际代码中使用.

console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为1

console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为2
// o.[[Prototype]]上还有一个‘b‘属性,但是它不会被访问到.这种情况称为"属性遮蔽".

console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4
console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有.
// o.[[Prototype]].[[Prototype]]为null,原型链已到顶端,没有d属性,返回undefined

继承方法

JavaScript 并没有真正的“方法”,JavaScript 只有函数,而且任何函数都可以添加到对象上作为对象的属性。继承的函数与其他的属性是基本没有差别的,包括“属性遮蔽”(这种情况相当于其他语言的方法重写)。

当继承的函数被调用时,this 指向的是当前继承原型的对象,而不是继承的函数所在的原型对象。

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// 当调用 o.m 时,‘this‘指向了o.

var p = Object.create(o);
// p是一个对象, p.[[Prototype]]是o.

p.a = 12; // 创建p的自身属性a.
console.log(p.m()); // 13
// 调用p.m时, ‘this‘指向 p. ‘this.a‘则是12.

使用不同的方法来创建对象和生成原型链

使用普通语法创建对象

var o = {a: 1};

// o这个对象继承了Object.prototype上面的所有属性
// 所以可以这样使用 o.hasOwnProperty(‘a‘).
// hasOwnProperty 是Object.prototype的自身属性。
// Object.prototype的原型为null。
// 原型链如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继承于Array.prototype (indexOf, forEach等方法都是从它继承而来).
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于Function.prototype(call, bind等方法都是从它继承而来):
// f ---> Function.prototype ---> Object.prototype ---> null

使用构造方法创建对象

在 JavaScript 中,构造方法其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g是生成的对象,他的自身属性有‘vertexes‘和‘edges‘.
// 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.

使用 Object.create 创建对象

ECMAScript 5 中引入了一个新方法:Object.create。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

遍历对象的属性时,原型链上的每个属性都是可枚举的。

检测对象的属性是定义在自身上还是在原型链上,有必要使用 hasOwnProperty 方法,该方法由所有对象继承自 Object.proptotype

hasOwnProperty 是 JavaScript 中唯一一个只涉及对象自身属性而不会遍历原型链的方法。

注意:仅仅通过判断值是否为 undefined 还不足以检测一个属性是否存在,一个属性可能存在而其值恰好为 undefined

不好的实践:扩展原生对象的原型

一个经常使用的不好实践是扩展 Object.prototype 或者其他内置对象的原型。

该技术被称为 monkey patching,它破坏了对象的封装性。虽然一些流行的框架(如 Prototype.js)在使用该技术,但是该技术依然不是好的实践,附加的非标准的方法使得内置的类型混乱。

扩展内置对象原型的唯一正当理由是移植较新 JavaScript 引擎的特性,如 Array.forEach

结论

在编写使用到原型继承模型的复杂代码前理解原型继承模型十分重要。同时,还要清楚代码中原型链的长度,并在必要时结束原型链,以避免可能存在的性能问题。更进一步,除非为了兼容新 JavaScript 特性,否则永远不要扩展原生对象的原型。

时间: 2024-10-13 10:12:21

js继承与原型链的相关文章

深入理解JS继承和原型链

对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来说,JavaScript 的语法是比较怪异的,这是由于 JavaScript 是一门动态语言,而且它没有类的概念( ES6 新增了class 关键字,但只是语法糖,JavaScript 仍旧是基于原型). 涉及到继承这一块,Javascript 只有一种结构,那就是:对象.在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接.这个原型对象又有自己的原型,直到某个对象的原型为null 为止

js继承之原型链继承

面向对象编程都会涉及到继承这个概念,JS中实现继承的方式主要是通过原型链的方法. 一.构造函数.原型与实例之间的关系 每创建一个函数,该函数就会自动带有一个 prototype 属性.该属性是个指针,指向了一个对象,我们称之为 原型对象.什么是指针?指针就好比学生的学号,原型对象则是那个学生.我们通过学号找到唯一的那个学生.假设突然,指针设置 null, 学号重置空了,不要慌,对象还存在,学生也没消失.只是不好找了. 原型对象上默认有一个属性 constructor,该属性也是一个指针,指向其相

js继承之原型链方式实现

温故而知新: 在之前的文章已经重点了解了原型对象,今天就理一理这个原型对象在原型链式的继承中的使用 function a(x,y){this.x=x;this.y=y;} //定义一个函数,当构造函数用(正经从程序用大写哈!) 原型对象上添加属性 a.prototype.ayuanxing="a原型属性"; new 一个对象看看咯: 同样的,定义一个b函数: function b(z){this.z=z}; b.prototype.byuanxing="b原型属性"

继承与原型链

对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来说,JavaScript 的语法是比较怪异的,这是由于 JavaScript 是一门动态语言,而且它没有类的概念(虽然 class 是个保留字,不能作为变量名来使用). 继承方面,JavaScript 中的每个对象都有一个内部私有的链接指向另一个对象,这个对象就是原对象的原型.这个原型对象也有自己的原型,直到对象的原型为 null 为止(也就是没有原型).这种一级一级的链结构就称为原型链. 虽然这通常会被称作 JavaScrip

继承和原型链

继承方面,JavaScript 中的每个对象都有一个内部私有的链接指向另一个对象,这个对象就是原对象的原型.这个原型对象也有自己的原型,直到对象的原型为 null 为止(也就是没有原型).这种一级一级的链结构就称为原型链. 虽然这通常会被称作 JavaScript 的弱点之一,实际上这种原型继承的模型要比经典的继承模型还要强大.虽然在原型模型上构建一个经典模型是相当琐碎的,但如果采取其他方式实现则会更加困难. 基于原型链的继承 继承属性 JavaScript 对象有两种不同的属性,一种是对象自身

关于JavaScript的原型继承与原型链

在讨论原型继承之前,先回顾一下关于创建自定义类型的方式,这里推荐将构造函数和原型模式组合使用,通过构造函数来定义实例自己的属性,再通过原型来定义公共的方法和属性. 这样一来,每个实例都有自己的实例属性副本,又能共享同一个方法,这样的好处就是可以极大的节省内存空间.同时还可以向构造函数传递参数,十分的方便. 这里还要再讲一下两种特色的构造函数模式: 1.寄生构造函数从形式上来看,这种模式和工厂模式并无区别: function Person(name, age, job){var o = new O

JS对象继承与原型链

1.以复制方式实现的继承 1.1浅拷贝 基本类型的复制 1 var parent = { 2 lanage: "chinese" 3 } 4 5 var child = { 6 name: "xxx", 7 age: 12 8 } 9 10 function extend(parent, child) { 11 var child = child || {}; 12 for (const propertype in parent) { 13 child[proper

js面向对象编程/原型链/继承 —— javascript

目录 js面向对象编程 js原型链 共享方法 原型继承 js面向对象编程 js面向对象编程不同于 java 的类和对象 JavaScript 不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程. js声明的构造函数,类似于普通函数的声明,但又不同, 实例对象时,如果不写new,就是一个普通函数,它返回 undefined. 但是,如果写了new,它就变成了一个构造函数,它绑定的 this 指向新创建的对象, 并默认返回 this,也就是说,不需要在最后写return th

快速理解JS中的继承与原型链

语言是用来表达的工具.当我们需要代指某个东西的时候,通常称其为一个对象.在编程语言中,对象并不像真实世界中那样随处可见,随口可以指代.通常我们只有少数的原生对象,剩下的,需要我们自己去创建.在Java语言中,创建一只会“咯咯咯”叫的鸡时,我们是这么做的: public class Chicken{ public void makeSound(){ System.out.println("咯咯咯"); } } Chicken littleChicken = new Chicken();