这是我参与更文挑战的第8天,活动详情查看: 更文挑战
前言
作为一名前端开发,原型链可谓是迈不过去坎,面试官总喜欢问一些原型链问题,比如构造函数的某个属性被修改了,对当前已经构造的实例对象有影响吗?原型被修改,对构造函数new的新实例对象有影响吗?实例对象可以修改构造函数的属性吗?
这时候,你会发现原型链的问题原来是如此的弯弯绕绕,不禁让人长叹一声:这原型链真™烦啊!
今天大冰块就来熬夜来详细谈谈原型链。 从__proto__
属性 、 constructor
属性 等11个方面详详细细的让你彻底搞懂它!
1. __proto__
属性
任何对象都有__proto__属性, 指向的是构造函数的prototype属性,就是原型。例如:
// 例1:
// 创建构造函数
function Person(){
}
// 声明实例对象
var p = new Person()
// 实例对象的原型和构造函数的原型
console.log(p.__proto__ === Person.prototype) // true
// 例2:
[] == [] //false
// push 来源于数组的原型,[]由new Array创建出来,[] 就是Array的实例
console.log([].push === [].push) // true
访问原型对象的两种方式:
- 通过构造函数访问到原型:构造函数的
prototype
- 通过实例对象访问到原型:实例对象的
__proto__
使用注意点:
不要通过实例对象来修改原型上的东西( __ 开头的东西,是浏览器的私有属性,不希望被修改,而且存在浏览器兼容性问题)。
// 不推荐这么做
// p.__proto__.age = 18
// 修改原型的成员 -- 推荐
// Person.prototype.age = 18
2. constructor 属性
原型中默认自带的属性, 指向了当前的构造函数。
function Person(){
}
console.log(Person.prototype) 如下:
console.log(Person.prototype.constructor) 如下:
再来看看实例的constructor和构造函数的constructor是否相等:
var p = new Person()
console.log(p.constructor == Person) // true
console.log(p.constructor == Person.prototype.constructor) // true
总结:
理解 原型三角 关系:
构造函数和原型:配偶 构造函数(妈妈), 通过prototype属性找到了原型(爸爸) 爸爸也可以通过 constructor属性找到妈妈
构造函数和实例之间的关系:母子 构造函数可以创建孩子,但是访问不到孩子 实例作为孩子, 孩子可以直接访问到妈妈吗?(不行)
实例和原型直接的关系: 父子 孩子通过__proto__访问到爸爸 孩子可以间接的访问到妈妈,实例.proto.constructor
3. 原型链
首先,我们要明确两点:
1,任何对象都有__proto__属性, 它指向原型对象。
2,原型对象也是对象,原型也有__proto__属性,指向了原型对象的原型对象,
原型对象也有自己的原型对象,这样形成的一条链式结构,就叫做原型链。
// 就好比,生成的这个对象有爸爸,爸爸的爸爸还有爸爸,这样形成的爸爸链式就是原型链。
function Person(){
}
// Person.prototype
// 实例对象
var p = new Person()
// Person.prototype // 实例对象的原型
// Person.prototype.__proto__ // 原型的爸爸
// Person.prototype.__proto__.constructor // 原型的妈妈
// Person.prototype.__proto__ === Object.prototype // 原型的爸爸判断
console.log(Object.prototype.__proto__) // undefined
// 实例对象p的原型链:
// p ==> Person.prototype ==> Object.prototype ==> null
4. 内置对象的原型链
// Array
var arr = new Array()
console.log(Array.prototype.__proto__ === Object.prototype) // true
console.log(Array.prototype.__proto__.constructor) // ƒ Object() { [native code] }
// 实例对象arr的原型链:
// arr ==> Array.prototype ==> Object.prototype ==> null
// Date
var date = new Date()
// 实例date的原型链:
// date ==> Date.prototype ==> Object.prototype ==> null
// Math 是个对象,不是个构造函数
Math.random()
console.log(Math.__proto__.constructor) // ƒ Object() { [native code] }
// Math的原型链:
// Math ==> Object.prototype ==> null
总结:
任何对象的原型链上都有Object.prototype
5. 属性的查找原则
属性查找(搜索)原则: 沿着原型链进行查找。
- 对象查找属性,首先在当前对象自身上查找,如果查找到了该属性,就直接返回
- 如果没有查找到,去对象的原型上进行查找,如果找到了,直接返回了
- 如果还没有找到,继续沿着原型链往上查找,直到Object.prototype,,如果找到了,直接返回
- 如果还没有找到,就返回 undefined
function Person(name){
this.name = name
}
Person.prototype.name = "大冰块"
Person.prototype.color = "yellow"
Object.prototype.gender = "male"
// p的原型链:
// p ==> Person.prototype ==> Object.prototype ==> null
var p = new Person("大冰块")
console.log(p.color) // "大冰块"
console.log(p.gender) // male
console.log(p.sex) // undefined
p.toString() // toString 是沿着原型链直到 Object.prototype 上找到的。
属性设置原则:
- 如果对象没有该属性,就添加该属性,值为赋值的值。
- 如果对象有该属性,就覆盖原来的值。
总结:
属性搜索原则:沿着原型链查找。 属性设置原则:有,就覆盖,没有,就添加。
6. hasOwnProperty()
语法: 对象.hasOwnProperty("属性")
作用: 判断该属性是否是对象自身的,如果是返回true,不是就返回false
function Person(name){
this.name = name
}
Person.prototype.gender = "male"
Object.prototype.sex = "female"
var p = new Person("大冰块")
console.log(p.hasOwnProperty("name")) // true
console.log(p.hasOwnProperty("gender")) // false
console.log(p.hasOwnProperty("sex")) // false
console.log(p.hasOwnProperty("leg")) // false
console.log(p.hasOwnProperty("hasOwnProperty")) // false
console.log(Object.prototype.hasOwnProperty("sex")) // true
7. in运算符和hasOwnProperty的区别
判断属性能否被对象访问到,不管属性是对象自身的,还是原型链上的,只要能够访问到,就返回true,如果访问不到就返回false。
语法: "属性" in 对象
// hasOwnProperty(): 判断属性是否是对象自身的
function Person(name){
this.name = name
}
Person.prototype.gender = "male"
Object.prototype.sex = "female"
var p = new Person("大冰块")
console.log("name" in p) // true
console.log("gender" in p) // true
console.log("sex" in p) // true
console.log("leg" in p) // false
console.log(p.hasOwnProperty("sex")) // false
// hasOwnProperty 使用场景:
// 使用在for...in循环里面,过滤出对象自身的属性
for(var k in p){
// 需求: 只遍历对象自身的,只打印对象自身的
if(p.hasOwnProperty(k)){
// 说明k这个变量的属性是对象自身的
console.log(k)
}
// for..in在遍历对象的时候,会把对象自身的以及原型链上的属性都遍历到了
console.log(k) // name gender sex
}
8. propertyIsEnumerable():
语法:对象.propertyIsEnumerable("属性")
作用:判断属性是否可遍历
- 判断属性是否是对象自身的
- 判断属性是否可遍历 只有满足了上面两个条件才返回true
var obj = {
name: "大冰块",
age: 18,
hobby: "男",
gender: "male"
}
for(var k in obj){
console.log(k)
}
Object.prototype.name = "lw"
for(var k in Object.prototype){
console.log(k)
}
console.log(Object.prototype) // {name: "lw", constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
// 说明Object.prototype上自带的成员默认都是不可变量的
// 手动给对象添加的属性默认是可遍历的
console.log(obj.propertyIsEnumerable("name")) // true
console.log(obj.propertyIsEnumerable("gender")) // false
console.log(Object.prototype.propertyIsEnumerable("hasOwnProperty")) // false
console.log(Object.prototype.propertyIsEnumerable("name")) // true
9. Object.defineProperty的使用
// 需求:给obj添加个不可遍历gender属性
var obj = {
name: "大冰块",
age: 20,
hobby: "男"
}
// Object.defineProperty() 语法: Object.defineProperty(obj, prop, desc)
// 参数说明:
// obj: 给对象添加属性,指定是哪个对象
// prop: 给对象添加什么属性,是个字符串
// desc: 属性描述符,是个对象
// 默认gender就是不可遍历的属性了
Object.defineProperty(obj, "gender", {
value: "female", // value 给这个对象的该属性设置默认值
enumerable: true, // enumerable 设置属性是否可遍历,true可遍历
writable: false // writable 设置属性是否可修改的,true可修改
})
obj.gender = "不详"
console.log(obj) // {name: "大冰块", age: 20, hobby: "男", gender: "female"}
console.log(obj.propertyIsEnumerable("gender")) // true
10. isPrototypeOf
语法: 对象A.isPrototypeOf(对象B)
作用: 字面上理解为对象A是否是B的原型,判断对象A是否在B的原型链上,对象A是否是B的祖宗
function Person(){
}
var p = new Person()
// 原型链
// p ==> Person.prototype ==> Object.prototype ==> null
// 原型链和构造函数没有关系
console.log(Person.isPrototypeOf(p)) // false
console.log(Person.prototype.isPrototypeOf(p)) // true
console.log(Object.isPrototypeOf(p)) // false
console.log(Object.prototype.isPrototypeOf(p)) // true
// 任何对象的原型链上都有Object.prototype
console.log(Object.prototype.isPrototypeOf([])) // true
11. instanceof和isPrototypeOf
语法:对象 instanceof 构造函数 作用::判断构造函数的prototype属性是否在对象的原型链上。如果在,就返回true(站在原型链上的角度上理解 instanceof的作用 )
// []的原型链:
// [] ==> Array.prototype ==> Object.prototype ==> null
console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
// 区别:作用是一样的,区别在于写法。
// 对象A.isPrototypeOf(对象B) // 判断A是否在B的原型链上,A是个原型对象
// 对象B instanceof A // 判断A的prototype属性是否在B的原型链上,A是个构造函数
var date = new Date()
console.log(Date.isPrototypeOf(date)) // false
console.log(Date.prototype.isPrototypeOf(date)) // true
console.log(date instanceof Date) // true
console.log(date instanceof Object) // true
console.log(date instanceof Date.prototype) // error: 语法错误
后记
相信经过这11个方面细节的解释,你一定对原型和原型链有了更为深刻清晰的认知,如果本篇文章由帮助到你,希望能点赞支持一下,不枉我熬夜这么久才写完这篇文章。如果有错误也欢迎指出交流。感谢阅读~
PS: 今天是参加掘金更文挑战的第8天啦,没有存稿的我,今天拼着熬夜还是更了这篇文章,一起加油吧~
虽然没多少人看,也还是给自己引引流吧~ 更文挑战的文章目录如下:
- 2021.06.01 《多图预警!详细谈谈Flex布局的容器元素和项目元素的属性~》
- 2021.06.02 《如何把css渐变背景玩出花样来》
- 2021.06.03 《如何使用SVG制作沿任意路径排布的文字效果》
- 2021.06.04 《3大类15小类前端代码规范,让团队代码统一规范起来!》
- 2021.06.05 《团队管理之git提交规范:大家竟然都不会写commit记录?| 周末学习》
- 2021.06.06 《如何控制css鼠标样式以及扩大鼠标点击区域 | 周末学习》
- 2021.06.07 《 纯css实现:仿掘金账户密码登录时,小熊猫捂眼动作切换的小彩蛋》