深入研究javascript原型链

6 篇文章 0 订阅

研究之前,我们先复习下?

构造函数

任何函数,只要通过new操作符来调用,那么它就可以作为构造函数 

prototype

prototype是函数的独有属性,这个属性是一个指针,从一个函数指向一个对象,它的含义是函数的原型对象也就是这个构造函数所创建的实例的原型对象。它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。

 我们去控制台打印看下

//创建一个函数
function fn(){}
//看看函数的prorotype是什么东东?打印出来的是否是个对象
console.log(fn.prototype);

控制台打印出来的是个对象,里面有两个参数,constructor指向构造函数fn ,__proto__指定Object

__proto__

__proto__是对象的独有属性,JS中函数也是一种对象,所以也有__proto__属性,__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象。它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象里找,如果还没有找到,继续往上找,直到原型链为null为止。

 我们验证下

function fn(){}
var f1=new fn()
//f1.__proto__是否指向fn的原型fn.prototype
console.log(f1.__proto__);
console.log(f1.__proto__===fn.prototype);

 控制台输出的 f1.__proto 和上面 fn.prototype 输出一样,可以得出  f1.__proto__ === fn.prototype 为 true 

 constructor

       constructors属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数。

 来看看

function fn(){}
var obj=new Object();
var f1=new fn()

console.log(fn.prototype.constructor,'f1')
console.log(fn.constructor,'fn')
console.log(obj.constructor,'obj')

//f1实例的constructor是指向它的构造函数 fn 
console.log(f1.constructor===fn) ; //true

控制台

f1的 constructor 指向它的构造函数  fn , fn 的 constructor  指向 Function ,obj 的  constructor  指向它的构造函数 Object。

经过上面的了解估计你大概已经懂了原型链,现在我们来总结下什么是原型链?

每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null(null为原型链的终点),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链

 经过本仙女多次画图,终于得出下面这个简单明了的关系图说明: 

 

 举个例子来说明下

//定义构造函数fn
function fn(){}
fn.prototype.name='M'
//通过new创建fn的实例对象f1
var f1=new fn()
f1.age=23
//实例对象的__proto__指向构造函数的原型对象
console.log(f1.__proto__===fn.prototype);//true
//原型对象的constructor指向构造函数
console.log(fn.prototype.constructor===fn);//true

 

 再来看下这个原型的原型对象是什么:

console.log(fn.prototype.__proto__)

        控制台输出的对象的constructor指向的是Object()这个构造函数,所以fn.prototype.__proto__===Object.prototype为true,在继续打印fn.prototype.__proto__.__proto__控制台输出null,这个函数的原型链到此结束。

所以这个f1的原型链就是:

    于是我们就可以得出:在原型链中的指向是 

     函数 → 构造函数 → Function.prototype → Object.protype → null 

实例访问属性或者方法的时候,遵循以为原则:

  1. 如果实例上面存在,就用实例本身的属性和方法。
  2. 如果实例上面不存在,就会顺着__proto__的指向一直往上查找,查找就停止。

举个例子说明如何修改原型链

//定义构造函数fn1
function fn1(){}
fn1.prototype.method=function(){
   return '别搞错,我是fn1原型链上面的method';
}
//添加属性到prototype上
fn1.prototype.name="fn1";

//定义构造函数fn2
function fn2(){}
fn2.prototype.method=function(){
   return '哎呀,你调用的是fn2上面的method';
}
//通过new创建fn的实例对象f1
var f1=new fn1()
//添加属性到f1对象上
    f1.age=23;
console.log( f1.method()); //别搞错,我是fn1原型链上面的method
//输出f1实例上面的age : 23 
console.log( f1.age );// 23
//f1上没有这个属性,就会沿着原型链去原型对象上去找,因为原型对象上有name属性,所以输出“fn1”
console.log( f1.name ) ; //fn1
//f1上没有这个属性,就会沿着原型链去原型对象上去找,原型对象上也没有这个方法,输出 undefined
console.log( f1.obj ) ; //undefined

//将f1的__proto__指向fn2的原型
f1.__proto__=fn2.prototype;
//此时f1实例的原型对象已经变成了fn2的原型对象

console.log( f1.method()); //哎呀,你调用的是fn2上面的method

一般来说,我们不建议手动去修改某个对象的原型,这会破坏掉原来的原型链。

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

使用语法结构创建的对象

var o = {a: 1};

// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null

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

// 数组都继承于 Array.prototype 
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null

使用构造器创建的对象

 

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

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

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

var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 '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

使用 class 关键字创建的对象

ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 classconstructorstaticextends 和 super

"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

性能

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

        遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法 。

console.log(g.hasOwnProperty('vertices'));
// true

console.log(g.hasOwnProperty('nope'));
// false

console.log(g.hasOwnProperty('addVertex'));
// false

console.log(g.__proto__.hasOwnProperty('addVertex'));
// true

   hasOwnProperty 是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys()

注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值