Skip to content

Latest commit

 

History

History
185 lines (175 loc) · 9.28 KB

原型链.md

File metadata and controls

185 lines (175 loc) · 9.28 KB

js中原型链

五个基础知识:

  1. 所有的函数都有 prototype 属性,指向该函数的原型对象;
  2. 所有的对象都有__proto__属性,指向该对象的构造函数的的原型对象;
  3. 由于js中万物皆对象,所以构造函数也有__proto__属性;
  4. 构造函数是通过Function构造函数构造出来的实例,所以构造函数的__proto__属性皆指向同一个对象,通过2可得,都是指向Function的原型对象;
  5. 所有构造函数的原型对象的constructor都指向构造函数本身

构造函数、构造函数实例、构造函数原型对象之间的关系

  1. js中的对象是通过构造函数创造出来的,构造函数的prototype指针指向了一个对象,我们可以叫它构造函数的原型对象,通过构造函数创造出来的实例上也有一个指针指向构造函数的原型对象。这样构造函数创建的实例就跟构造函数的原型对象就关联起来了。
  2. 构造函数原型对象的constructor指针指向构造函数,构造函数的prototype指针指向其原型对象。

instanceof 操作符

typeof操作符类似,instanceof是用来判断对象类型的,使用该操作符的表达式返回值是布尔值,true表示第一个操作数是第二个操作数的实例,并且具有继承关系的对象也可以返回true

function A() {};
function B() {};
let a = new A();
a instanceof A // true
a instanceof B // false
a instanceof Object // true

上面例子中说明了对象a是构造函数A的实例,同时也是构造函数Object的实例,但是不是构造函数B的实例。我们思考一下:为什么会有这种结果?我们可以根据构造函数、构造函数实例、构造函数原型对象之间的关系来了解。 a 与 A之间的关联关系是因为二者都有一个指针指向同一个对象(构造函数原型对象),那么我们可以验证一下instanceof操作符是否与此有关。

a.__proto__ = B.prototype
a instanceof A // false
a instanceof B // true
a instanceof Object // true

通过以上示例可以得出instanceof操作符的结果确实反应的是实例对象跟原型对象之间的关系。为什么 a也是Object的实例呢?通过上面的分析可以得出a与Object的原型对象存在某种联系。

function C() {};
let objC = new C();

上面示例中,function C是一个函数,函数也是一个对象。在js中函数是通过Function构造函数创建的,也就是说C.__proto__ === Function.prototype。同样来说,Function.prototype这个对象的__proto__也有一个相关的构造函数,我们看看这个对象的构造函数是什么。

Function.prototype.__proto__.constructor // [Function: Object]

也就是说Function.prototype对象是Object构造函数创建的。

Function.prototype.__proto__ === Object.prototype // true

说明了Function的原型对象的__proto__指针指向了Object的原型对象。 我们再来整理一遍上面的问题。

objC.__proto__ === C.prototype;  // true
C.__proto__ === Function.prototype;  // true
Function.prototype.__proto__ === Object.prototype; // true
C.prototype.__proto__ === Object.prototype; // true

我们可以发现objC与Object.portotype之间通过__proto__实现了关联,也就是说这种关联关系可以通过instanceof操作符来判断是否存在。这种关联关系就是原型链。 再看一组示例:

Function instanceof Function // true
Function instanceof Object // true
Object instanceof Function // true
Object instanceof Object // true

通过前面的探讨我们可以解释上面问题:

  1. Function是构造函数,所以既有prototype指针,又有__proto__指针。由于Function instanceof Function结果为true,所以说明Function.__proto__Function.prototype存在原型链关系。我们进一步作出判断Function.__proto__ === Function.prototype // true,所以说明Function__proto__Functionprototype指向同一个对象。
  2. Function instanceof Object为true说明Function.__proto__Object.prototype存在原型链关系,我们进一步作出判断Function.__proto__.__proto__ === Object.prototype // true,说明Funciton的原型对象的__proto__指针指向了Object的原型对象。
  3. Object instanceof Function为true说明Object.__proto__Function.prototype存在原型关系,进一步判断Object.__proto__ === Function.prototype // true说明Object构造函数是Function的实例对象。
  4. Object.__proto__Function.prototype指向相同,由于Function.prototypeObject.prototype存在原型链关系,所以Object instanceof Object的结果为true。

通过以上的分析我们可以通过一张图来说明js中各对象间原型链关系

new 关键字的作用

  1. 创建了一个对象
  2. 将函数作用域与创建的对象绑定
  3. 通过原型链实现对象与构造函数的原型关联
  4. 返回创建的对象

创建对象的几种方式

  1. Object构造函数
let person = new Object();
person.name="king";
person.say=function(){
	console.log(this.name)
}

缺点: 1.1 写法繁琐,如果存在多个对象需要重复编写相同的属性或方法 1.2 无法给实例分类,因为得到的所有对象都是Object的实例。js给我们提供了几个原生的类型:Object、Array、RegExp、Date等,我们自己创建的对象虽然都是Object类型,但是有时候我们需要更加详细的类型。例如我们希望将person实例和animal实例区分开,就需要我们自己定义Person类型和Animal类型。 2. 对象字面量

let person = {
	name: "king",
	say: function() {
		console.log(this.name)
	}
}

缺点同上 3. 工厂方式 为了解决具有相同属性的多个对象的创建,我们可以通过一个函数的形式来进行对象的创建。在这个函数中我们只需要通过传入形参的办法区分不同的属性或者方法。我们把这个用来创建对象的函数叫做工厂函数。所以这种方式也就称为工厂方式。

// 工厂函数
function createPerson(name) {
	let person = new Object();
	person.name = name;
	person.say = function() {
		console.log(this.name);
	}
	return person;
}
// 创建对象
let person1 = createPerson('king1');
let person2 = createPerson('king2');

通过上面的方式我们可以迅速地创建多个拥有say方法的person实例,通过这样的方式我们就可以减少冗余的代码了。 缺点:工厂方式还是没能解决对象类型的问题 4. 构造函数 我们通过js提供的原生类型进行对象创建的时候,往往是使用原生类型的构造函数进行对象的创建,那么我们在创建我们自定义类型的对象的时候,就需要我们有自定义的构造函数。其实构造函数就是普通的函数,只不过我们在写法上要跟普通的函数进行区分,一般来说构造函数首字母大写,普通函数首字母小写。我们在拥有了构造函数之后就可以像原生类型一样通过new关键字进行对象的创建。

// 构造函数
function Person(name) {
	this.name = name;
	this.say = function() {
		console.log(this.name);
	}
}
function Animal() {}
// 创建对象
let person1 = new Person('king1');
let person2 = new Person('king2');

js中提供了instanceof操作符来进行对象类型的判断,返回值是布尔值,true表示被判断的对象是当前构造函数的实例,false表示被判断的对象不是当前构造函数的实例。

person1 instanceof Person // true
person1 instanceof Animal // false

缺点:我们通过构造函数形式创建对象已经可以解决了Object构造函数创建方式的两个问题了,但是这里还有一个问题,其实有些内容我们是可以共用的,例如say方法没有必要每个对象都有自己的say方法。

person1.say === person2.say // false
  1. 原型模式 为了解决上述问题,我们可以将对象的属性和方法添加到构造函数的远行对象上,这样我们创建的对象就可以通过原型链查找到相关的属性和方法。
// 构造函数
function Person() {}
Person.prototype.name = 'king';
Person.prototype.say = function() {
	console.log(this.name);
}
Person.prototype.tools = {
	hammer: 1,
	axe: 1
}
// 创建对象
let person1 = new Person();
let person2 = new Person();

通过这种方式我们可以将多个对象的属性和方法共用

person1.say === person2.say // true
person1.tools === person2.tools // true

但是原型方式同样会有问题,如上例所示,修改person1的tools中的hammer值,同样会影响到person2的值。

person1.tools.hammer = 2;
console.log(person2.tools.hammer) // 2

所以我们应该将公用的属性和私有的属性进行区分,也就是组合构造函数和原型模式。 6. 组合构造函数和原型模式 为了解决原型模式的问题,我们可以将私有的属性或方法放到构造函数中,将共有的属性或方法放到构造函数的原型对象上。

// 构造函数
function Person(name, tools) {
	this.name = name;
	this.tools = tools;
}
// 原型方法
Person.prototype.say = function() {
	console.log(this.name);
}

通过混合使用构造函数方式和原型方式创建的对象基本上就可以解决我们遇到的问题了。