手写 new
用法
function Person(name){
this.name=name;
this.sayname=function(){
console.log(this.name)
}
}
var sbn = new Person ('撒贝宁');
sbn.sayname(); // 撒贝宁
在调用 new 的过程中会发生以下四件事:
- 新生成一个对象
- 将构造函数的作用域赋值给新对象(即绑定新对象的 this)
- 执行构造函数中的代码(即为这个新对象添加属性和方法)
- 返回新对象
代码
function Person(name){
this.name=name;
this.sayname=function(){
console.log(this.name)
}
}
// ...args为ES6展开符,如果使用arguments要先将其转换为数组
function _new(ctx, ...args) {
// 创建空对象
let obj = new Object();
// 连接新对象原型,新对象可以访问原型中的属性
obj._proto_ = ctx.prototype;
// 执行构造函数,即绑定this,并且为这个对象添加属性
let res = ctx.call(obj, ...args);
// 如果函数返回的事null或者undefined则返回obj,否则返回res
return res instanceof Object? res:obj;
}
var sbn = _new(Person, '撒贝宁')
sbn.sayname(); // 撒贝宁
总结
new一个对象的过程:
- 创建一个空对象
- 对新对象进行
prototype绑定 - 将新对象和函数调用的this绑定起来
- 执行构造函数的方法
- 如果函数没有返回值则自动返回这个对象
手写 instanceof
作用
用来检测一个对象是否是某个构造函数的实例对象,如果是则返回true,如果不是,则返回false。通过 === 严格等于来比较两个原型是否相等。
原型对象和原型链
函数即对象,每个函数都有一个prototype属性,这个属性就是函数的原型对象,在原型对象上定义的属性或方法,会被该函数的实例对象所继承,实例对象可以直接访问到原型对象上面的属性或方法。
prototype属性默认值为空对象,这个空对象正是来自Object的构造函数。
__proto__属性是在实例被创建的时候,js引擎为实例自动添加的一个属性,等于其构造函数的prototype属性,也就是说实例的__proto__就是这个实例的原型对象。
在代码中我们可以直接操作prototype属性,prototype被称为显式原型对象。代码上不推荐直接访问__proto__,__proto__被称为隐式原型对象。
当实例要访问某一个属性的时候,首先会在实例自身查找,如果没有找到,就继续沿着__proto__对象查找,如果还是没找到,则继续对__proto__的__proto__对象查找,如果最终找到__proto__对象为null时都没有找到,就返回属性未找到的错误,如果找到了就返回该属性值。这个由若干个__proto__对象串联起来的查找路径,就称为原型链。
代码
// L实例对象,R构造函数
function _instanceof(L, R) {
let l = L.__proto__;
let r = R.prototype;
while (true) {
if (l == null) {
return false;
}
if (l === r) {
return true;
}
// 如果本轮没找到,则沿着__proto__继续向上查找
l = l.__proto__;
}
}
console.log(_instanceof([1,2,3], Array)) // true
总结
函数接收两个参数,第一个是被检测的对象,第二个是实例的构造函数。然后循环比较实例的__proto__和构造函数的prototype是否相等,当__proto__为null的时候停止循环并返回false,当找到了就返回true。