js原型

什么是原型

一个新创建的函数自身都会带一个prototype属性,这个属性指向这个函数的原型对象,原型对象自带一个constructor属性,这个属性指向这个函数。

构造函数、原型、实例,三者关系

构造函数的prototype属性指向原型,原型的constructor属性指向构造函数,构造函数通过new操作符创建实例,实例继承原型的所有属性。Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__来访问原型。

function Person() {}	// 构造函数默认首字母大写
console.log(Person.prototype)	// 默认自带constructor的原型对象
// {constructor: ƒ}
// 为原型添加属性
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
};
let person1 = new Person()	// 创建实例
let person2 = new Person()
console.log(person1.name)	// 从原型继承的name属性
// "Nicholas"
console.log(person1.__proto__)		// 实例访问原型对象
//{name: "Nicholas", age: 29, job: "Software Engineer", sayName: ƒ, constructor: ƒ}

在这里插入图片描述
通过Object.getPrototypeOf()方法来访问实例的原型

console.log(Object.getPrototypeOf(person1))
// {name: "Nicholas", age: 29, job: "Software Engineer", sayName: ƒ, constructor: ƒ}

注意:前面提到过,原型最初只包含 constructor 属性,而该属性也是共享的,因此
可以通过对象实例访问。

console.log(person1.constructor)	// 实例访问构造函数
// Person() {}

实例中属性读取顺序

实例可以继承原型的属性,但是不能更改原型中的值。当读取一个实例的属性时,可能会执行两次搜索。例如sayName,解析器会先问:person1中有没有sayName?回答:没有。接下来解析器再问:person1原型中有sayName吗?回答有。

person1.name = 'uzi'	// 覆盖原型中的name
console.log(person1.name)	// 来自实例
// 'uzi'
console.log(person2.name)	// 来自原型
// "Nicholas"

属性和原型、实例

js有几种方法可以判断一个属性是属于原型还是实例

in操作符

in操作符就是for-in中的in,in操作符通过对象可以访问到属性时就返回true,不论属性是在实例中还是在原型中。

console.log(person1.name)	// 来自实例
// true
console.log(person1.age)	// 来自原型
// true
for (let i in person1){		// 遍历对象中所有可以访问的属性
	console.log(i)
}
// name
// age
// job
// sayName

hasOwnProperty()

hasOwnProperty()只在属性存在于实例中时才返回 true(不管是否可枚举)

console.log(person1.hasOwnProperty('name'))	// 来自实例
// true
console.log(person1.hasOwnProperty('age'))  // 来自原型
// false

Object.keys()

取得对象上所有可枚举的实例属性。

console.log(Object.keys(person1))	// 所有可枚举的实例属性
// ["name"]

Object.getOwnPropertyNames()

取得对象上所有实例属性,不管可不可枚举。

Object.defineProperty(person1, 'firend', {	// 添加不可枚举属性,firend
	configurable: true,
	value: 'xiaohu',
	enumerable: false
})
console.log(Object.getOwnPropertyNames(person1))	// 所有实例属性
// ["name", "firend"]
console.log(Object.keys(person1))	// 所有可枚举的实例属性
// ["name"]

更简单的原型语法

每次为原型添加属性都一个一个来,很是麻烦,为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

function Person() {}
Person.prototype = { 
	 name : "Nicholas", 
	 age : 29, 
	 job: "Software Engineer", 
	 sayName : function () { 
	 alert(this.name); 
	 } 
}

好是好,但是有一个弊端,就是原型的constructor指向不在是Person(),而是Object()。因为原型使用字面量的形式被重写。字面量创建的对象构造函数都是Object()。

console.log(person1.constructor)	// 原型的constructor指向
//  Object()

要是又想用字面量重写原型,又想让原型的constructor指向其构造函数,怎么办呢?
重写一下constructor就行了。。。

Person.prototype = {	// 重写原型并且constructor指向Person
	 constructor: Person,
	 name : "Nicholas", 
	 age : 29, 
	 job: "Software Engineer", 
	 sayName : function () { 
	 	alert(this.name); 
 	} 
}
let person3 = new Person()
console.log(person3.constructor)	// constructor指向Person
// Person()

原型的动态性

当解析器在查找某一对象属性时,会在原型上进行一次搜需,所以,即使先创建实例后修改原型,也可以达到预期效果,看下面这个栗子。

// 本例子从头开写,与上面例子无关
function Person() {}
let firend = new Person()
Person.prototype.name = 'uzi'
console.log(firend.name)
// 'uzi'

其实在创建实例时,实例就有一个指针指向原型,每当查找属性时,都会沿着这条线去找到原型进行查找,无论何时修改原型。但是如果重写了原型就不一样了,老的原型对象和新的原型对象都存在。但创建实例时指向老原型的指针不会变。看栗子。

// 本例子从头开写,与上面例子无关
function Person() {}
let firend = new Person()
Person.prototype= { 
	constructor: Person, 
	name : "Nicholas", 
	age : 29, 
	job : "Software Engineer", 
	sayName : function () { 
		alert(this.name); 
	} 
};
console.log(firend.name)	//error

在这里插入图片描述

总结

构造函数、原型、实例三者指向关系:构造函数.prototype→原型,原型.constructor→构造函数,实例.__proto__→原型。实例与原型之间通过指针松散连接关系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值