原型与原型链
原型对象
构造函数可以通过 new 的方法来创建一个实例,而每一个构造函数都有一个 prototype 的属性指向一个原型对象。原型对象中所有的属性与方法都可以被该函数所创建的实例所共享。
原型链
每个被构造函数创建出来的实例都有一个隐式的属性__proto__,该属性指向构造函数的 prototype,即原型对象。浏览器在取对象中的方法时,会先在对象中寻找是否存在该方法,如果没有则通过__proto__到其原型对象上寻找,以此一层层通过__proto__,直至为 null。
原型链是基于 proto 形成的,继承是通过 prototype 实现的
Object,Function,__proto__,prototype
在 JS 中,万物皆对象,所以函数(方法)也是一个对象。
所有的对象都有一个根源 Object.prototype,Object.prototype 只是一个普通对象
根源的原型对象是 null
但是 null 却不是一个对象,虽然用 typeof 验证 null 为 object,这个是 JS 的 bug。
(typeof Object.prototype) === object;//true
Object.prototype.__proto__=== null;//true
Object.prototype.prototype === undefied;//true
特例:__Object 和 Function 既是对象,又是函数,两者内部同时含有 proto 和 prototype 属性,他们关系较为复杂__Function.prototype 指向“__内置函数__”,而 Object.prototype 指向“__根源对象__”
Object.__proto__ === Function.prototype //true
Object.__proto__ === Function.__proto__//true
Object.prototype === Function.prototype.__proto__ // true
// 因此
Function instanceof Object //true
Object instanceof Function //true
x instanceof y,当 y 的原型对象在 x 的原型链之上,返回 true,否则返回 false
构造函数与普通函数
创建方式相同
构造函数也是个普通函数,但是一般来说,构造函数会采用首字母大写的方式。
调用方式不同,目的不同
普通函数:直接调用,返回值为函数调用的之后 return 的值,无 return 则为空;
构造函数:需要用 new 调用,以创建实例的方式来调用,目的是创建一个指向构造函数的原型的一个实例对象。
构造函数调用创建实例的时候内含多步骤
这个问题可以换个角度说,就是当我们再 new 一个对象的时候,内部发生了什么。
1、首先需要创建一个空对象。
2、将空对象的__proto__指向构造函数的 prototype(构造函数的原型对象),以此获得原型的方法与属性
3、绑定 this 的指向
4、返回新对象
在文章下方会手动实现一个简易版的 new 方法
构造函数用 this 构造内部方法与属性
function Cat(name, age) {
this.age = age
this.name = name
this.run = run
function run() {}
}
let cat = new Cat('米粒', '6 个月')
构造函数所创建的实例都可以用 instanceof
可以用来验证该实例是否由验证的构造函数所创建的。原理就是去查找实例的__proto__是否与构造函数的 prototype 所指向的原型对象是同一个。
文章下方将会手写一个简略版的 instanceof
手写 new
当我们再 new 一个对象的时候,内部发生了什么。
1、首先需要创建一个空对象。
2、将空对象的__proto__指向构造函数的 prototype(构造函数的原型对象),以此获得原型的方法与属性
3、绑定 this 的指向
4、返回新对象
function Cat(name, age) {
this.age = age
this.name = name
this.run = run
function run() {}
}
function _new() {
//
let newObject = new Object()
// arguments 是传入函数的参数,因为是类数组的形式, 无法直接使用 shift 方法
// 所以使用 ES6 的新特性进行转换
// Array.prototype.shift.apply(arguments)用数组原生方法也是可以的
arguments = Array.from(arguments)
// 取出构造函数
let Constructor = arguments.shift()
// 获得构造函数的原型对象上的方法与属性
newObject.__proto__ = Constructor.prototype
// 绑定 this 的指向
Constructor.apply(newObject, arguments)
return newObject
}
cat = _new(Cat, '米粒', '6 个月')
console.log(cat)
// Cat {age: '6 个月', name: '米粒', run: [Function: run] }
手写 instanceof
原理:查找实例的__proto__是否与构造函数的 prototype 所指向的原型对象是同一个。
function instance_of(L, R) {
const R_prototype = R.prototype
let L_proto = L.__proto__
let flage = false
while(L_proto !== null) {if (L_proto === R_prototype) {
flage = true
break
}
L_proto = L_proto.__proto__;
}
return flage
}
// 使用上方的_new 方法来创建实例
cat = _new(Cat, '米粒', '6 个月')
console.log(cat)
console.log(cat instanceof Cat) // true
console.log(instance_of(cat, Cat)) // true
console.log(instance_of(cat, Object)) // true
console.log(instance_of(cat, {})) // false
console.log(instance_of(cat, Boolean)) // false