你不知道的JS(七):深入原型链

这是我阅读《You Don’t Know JavaScript》的过程中,记录下来的读书笔记。

一、new操作的问题

1. 副作用

  • 前面提到,new操作的过程中,其实是将构造函数作为实例对象的方法进行执行。
  • new操作的目的是产生对象。那么这里就会有一个问题,比如说,在这个构造函数执行的过程中alert()什么内容,其实也是会顺利弹出。
  • 这也就是产生了副作用,因为这不是我们希望发生的。

代码演示:

function Father() {
  this.a = '1'
  this.b = '2'
  var v = '副作用'
  alert(v)
}
var k = new Father()
console.log(k.a) // '1'
console.log(k.v) // undefined

该代码执行过程中,会弹出提示框:

在这里插入图片描述

2. 返回值

  • 还有一个问题,我们都知道执行构造函数的过程中,为实例对象添加属性和方法。那如果构造函数返回的是一个对象呢?
function Father() {
  this.a = '1'
  return {
    b: '2'
  }
}
var k = new Father()
console.log(k.a) // undefined
console.log(k.b) // '2'
  • 其实就会将返回的对象做为实例对象
  • 如果返回的不是对象,则不会有这个问题。

二、constructor

1. 位置误解

  • 前面说到constructor时,原型对象B的constructor属性指向构造函数A,也就是B.constructor === A是成立的。这意味着B有constructor属性。

  • 其实不是这样的,constructor属性其实在A.prototype上。所以是A.prototype.constructor指向构造函数A。

  • constructor的错误理解很容易产生误导。

  • 比如:A.prototype的constructor属性只是在函数A在声明时的默认属性。如果你创建了一个新对象,并用这个新对象替换了函数默认的原型对象,那么新对象并不会自动获取constructor属性。

function Foo() {...}
Foo.prototype = {...}// 创建一个新原型对象

var a1 = new Foo()
a1.constructor === Foo;// false
a1.constructor === Object; // true
  • 从上面代码可以看出a1是有Foo构造的,那么a1.constructor为什么是指向Object而不是Foo。而且a1为什么有constructor属性?
  • 其实a1并没有constructor属性,所以它会通过原型链上的Foo.prototype访问到。如果这个对象也没有constructor属性。就会继续向上查找,一直到顶层的Object.prototype。这里就有一个constructor属性,指向内置的Object()函数。

2. 不可靠性

  • 当然,你也可以手动给Foo.prototype添加一个constructor属性,不过需要符合正常行为的不可枚举属性。
  • 而对象的constructor属性,会默认指向一个函数,这个函数可以找到prototype上的constructor属性。

从上面可以看出,constructor属性是非常不可靠并且不安全的引用。

三、__proto__

1. 作用

绝大多数浏览器支持这一种方法来访问原型对象:

a.__proto__ === Foo.prototype;// true
  • 甚至可以直接通过,__proto__.__proto__...来遍历原型链
  • 和constructor一样,__proto__实际上并不存在于对象a中,也是可设置的。

比如:__proto__存在于Object.prototype上。

2. 理解

  • __proto__看起来很像属性,实际上更像一个getter/setter

它的实现大致是这样的:

Object.defineProperty(Object.prototype, "__proto__") {
	get: function() {
		return Object.getPrototypeOf(this);
	},
	set: function() {
		Object.setPrototypeOf(this, o);
		return o;
	}
}

四、对象关联

  • 原型链其实就是存在于对象中的一个内部链接,对象可以通过它引用其他对象。
  • 这个链接的作用:如果在对象上没有找到需要的属性或方法引用,可以在__proto__上关联的对象上继续查找。同理,在后者中也没有找到需要的引用就会继续查找它的__proto__,以此类推。这一系列对象的链接就成为原型链。

除了前面通过new操作的方式,我们可以使用其他方法关联对象。

1. 复制引用

通过前面的内容,我们知道,原型对象的作用就是共享属性和方法。

  • 而且原型对象本身就是对象,构造函数和实例对象中保存的是引用。我们可以将引用直接复制到其他地方
  • 比如new操作里面,将构造函数的prototype属性包含的原型对象引用复制给实例对象的__proto__属性。
  • 比如,我们将对象A作为函数B和函数C的原型对象,函数B和函数C进行new操作创建的对象就能访问到原型对象A的属性。

那么,如果我想让函数D创建的对象也能访问到对象A中的属性,那该怎么做?

  • 其实很简单,直接赋值操作就可以了,是不是很简单。
D.prototype = B.prototype
  • 上面代码中,D的prototype属性引用了B的原型对象,也就是两个函数的原型对象是同一个。
  • 这种方式很简单,但是我们很少这样做,因为当我们修改D.prototype的时候,B.prototype也会发生变化,也就是B的原型对象也变了。多数情况下,我们是不想出现这种问题。
  • 而且,后续还要进行new操作才能产生实例对象。

那么,又要能访问到,又不想影响到,有没有这样的方法呢?是原型链继承,后面会讲到

下面两个方法,就不需要通过构造函数了,直接是建立实例对象与原型对象的关系。

2. Object.create()

该方法的作用是创建一个新对象,使用现有的对象作为新对象的原型对象。这个是比较常用的方法。

Object.create(proto, [propertiesObject])

参数

proto:必须。表示新建对象的原型对象,即该参数会被赋值到新对象的原型上。

propertiesObject:可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。

比如:

var foo = {
	somthing: function() {
		console.log("Tell me something");
	}
};
var bar = Object.create(foo);
bar.something(); // Tell me something
Object.create(null) // 创建出没有原型对象的实例对象

优点:没有new操作时,调用构造函数产生的副作用。

缺点:该方法时创建新对象,不能修改既有对象,也就是需要把原有对象替换掉。

Object.create() 的好处

Object.create()可以直接关联起两个对象,避免了中间不必要的麻烦(比如:new操作)

补充:JS中只有两种创建对象的方法

  • 我们知道js中创建一个自定义对象有两种方法,一种是使用new,另一种是使用对象字面量形式。
  • 至于构造函数模式、工厂模式、原型模式、组合模式、寄生模式、es6中的class、Object.create()等等,都不过是这两种方法的组合应用。

新建对象需要把原来的废弃掉,那有没有一个在既有对象上修改的方法呢?

Object.setPrototypeOf()

用来设置一个对象的 prototype 对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

Object.setPrototypeOf(object, prototype)

代码示例

var proto = {
    y: 20,
    z: 40
};
var o = { x: 10 };
Object.setPrototypeOf(o, proto);

五、检查“类”关系

1. instanceof()

function Foo() {
	// ...
}
Foo.prototype.blah = ...;
var a = new Foo();

怎么判断a的“类”?

a instanceof Foo; // true

instanceof判断原理是:在a的原型链上,是否有指向Foo.prototype的对象。

这个方法只能用于实例对象和构造函数之间。

2. isPrototypeOf()

Foo.prototype.isPrototypeOf(a) // true

原理:在a的原型链上是否存在Foo.prototype

在es5中是

Object.getPrototypeOf(a) === Foo.prototype // true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值