原型链
- 创建 (声明) 对象有几种方法
- 原型、构造函数、实例、原型链
instanceof
的原理- new 运算符
一. 创建对象有几种方法
1.字面量
var test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.__proto__.x);//undefined
console.log(test2.__proto__.x === test2.x);//false
2.构造函数new
// 方法1 #通过new Object声明的一个对象
var test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.__proto__.x);//undefined
console.log(test1.__proto__.x === test1.x);//false
// 方法2 #使用显式构造函数创建对象
var M = function(name){ this.name = name;};
var o3 = new M('o3'); //M {name: "o3"}
new的作用: 1.创了一个新对象; 2.this指向构造函数; 3.构造函数有返回,会替换new出来的对象,如果没有就是new出来的对象
3.内置方法
Obejct.create(obj,descriptor)
,obj是对象,describe描述符属性(可选),创建一个具有指定原型且可选择性地包含指定属性的对象。
let p = {x:123,y:345};
let test = Object.create(p);
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//3
console.log(test.__proto__.x === test.x);//true
console.log(test.__proto__ === p) // true
Object.create
方法是把参数中这个对象作为一个新对象的原型对象赋给test
的,test
本身不具备这个属性的,通过原型链来连接这个原型对象的。(所以test
对象本身没有name这个属性,只能通过原型链来找name属性。
三种方法的优缺点
- 功能:都能实现对象的声明,并能够赋值和取值
- 继承性:内置方法创建的对象继承到__proto__属性上
- 隐藏属性:三种声明方法会默认为内部的每个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是可以读取和可配置的,属性分类见下面:
- 属性读取:
Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptors()
- 属性设置:
Object.definePropertype或Object.defineProperties
二. 原型链的关系
下图仔细观察原型、构造函数、对象实例、原型链之间的关系。
1.对象实例
只要是对象就是一个实例;回顾上面的创建对象的几种方式,任何一个实例对象都有一个隐式原型__proto__
对象。
2.构造函数
凡是通过关键字new
来操作后边的函数,这个函数就是构造函数;准确的说任何一个函数只要被new使用了,后面这个函数就可以被叫做构造函数。
构造函数可以使用new
运算符来生成一个实例;
3.原型对象
任何一个函数都有一个prototype
属性,他是函数所独有的,这个prototype
指的就是显式原型对象;
任何一个实例对象都有一个__proto__
对象。他是对象所独有的,这个__proto__
指的就是隐式原型对象;
__proto__
是原型链查询中实际用到的,它总是指向 prototype
;
prototype
是函数所独有的,在定义构造函数时自动创建,它总是被__proto__
所指。
4.原型链
每个对象都可以有一个原型_proto_
,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找知道终点null
...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了
原型链是通过什么来实现这个往上找的过程呢?
通过prototype这个原型和__proto__属性来完成原型链的查找的;
let obj1 = {name:'lucas'};
obj1.__proto__ === Object.prototype // true
obj1.__proto__.__proto__ // null #这是原型链顶端了
Object.prototype.__proto__ // null #这是原型链顶端了
function Person(){}
Person.prototype.__proto__.__proto__ // null #这是原型链顶端了
let person = new Person();
person.__proto__.__proto__.__proto__ // null #这是原型链顶端了
person.__proto__ === Person.prototype // true
function M (name) {
this.name = name;
}//person是构造函数
var o3 = new M('o3') // personTwo是实例
原型对象都有一个默认的constructor属性指向构造函数
三. instanceof 原理
instanceof
主要用于判断某个实例是否属于某个类型,也可用于判断某个实例是否是其父类型或者祖先类型的实例。
instanceof
主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
实例对象上有__proto__这个属性,实例对象的这个属性引用是它构造函数的原型对象(也就是找到的这个构造函数);
构造函数有prototype这个属性,这个属性引用的原型对象,在往下走,实例对象的__proto__这个属性,其实是引用这个原型对象。
模拟开发instanceof
function instanceof(left, right) {
const rightVal = right.prototype
const leftVal = left.__proto__
// 若找不到就到一直循环到父类型或祖类型
while(true) {
if (leftVal === null) {
return false
}
if (leftVal === rightVal) {
return true
}
leftVal = leftVal.__proto__ // 获取祖类型的__proto__
}
}
四. new 运算符
var new2 = function(func){
//1.创建一个空对象,这个对象要继承这个构造函数的原型对象(空对象要关联构造函数的原型对象;)
let o = Object.create(func.prototype);
//2.执行构造函数
let k = func.call(o);//call用来转移上下文(this),把这个上下文转成o对象
//3.判断构造函数的运行结果是不是对象类型
if(typeof k ==='object'){
return k;
}else{
return o;
}
};