1.手写 call、apply 及 bind 函数
首先要从以下几点来考虑如何实现这几个函数:
不传入第一个参数,那么上下文默认为 window
改变了 this
指向,让新的对象可以执行该函数,并能接受参数
window.x = 100;
(1)实现 call
函数
Function.prototype.myCall = function(context) {
if (typeof this !== 'function') throw Error('this not a function');
context = context || window;
context.fn = this;
// var args = [].slice.call(arguments).slice(1);
// var result = context.fn(...args); // ES6
// ES3
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
};
// 测试一下
function sum(a, b) {
this.a = a;
this.b = b;
return this.x + a + b;
}
var sum1 = sum.myCall(null, 1, 2);
var sum2 = sum.myCall({ x: 1 }, 1, 2);
console.log(sum1, sum2); // 103 4
以下是对实现的分析:
- 首先 context 为可选参数,如果不传的话默认上下文为 window
- 接下来给 context 创建一个 fn 属性,并将值设置为需要调用的函数
- 因为 call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
- 然后调用函数并将对象上的函数删除
(2)实现 apply
函数
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') throw Error('this not a function');
context = context || window;
context.fn = this;
// 处理参数和 call 有区别
var result = arguments[1] ? context.fn(arguments[1]) : context.fn();
delete context.fn;
return result;
};
// 测试一下
function count(arr) {
var sum = 0;
for (var i = 0, len = arr.length; i < len; i++) {
sum += arr[i];
}
return this.x + sum;
}
let count1 = count.myApply(null, [3, 4]);
let count2 = count.myApply({ x: 1 }, [3, 4]);
console.log(count1, count2); // 107 8
apply 的实现类似于 call,区别在于对参数的处理,所以就不一一分析思路了
(3)实现 bind
函数
bind 的实现对比其他两个函数的实现稍微复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') throw Error('this not a function');
var _this = this;
var args = [].slice.call(arguments).slice(1);
return function F() {
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
}
};
// 测试一下
function mySum(a, b) {
this.a = a;
this.b = b;
return this.x + a + b;
}
let mySum1 = mySum.myBind({ x: 1 });
console.log(mySum1(1, 2)); // 4
console.log(mySum1([1, 2])); // 4
let mySum2 = mySum.myBind({ x: 10 }, 1);
console.log(mySum2(2)); // 13
let mySum3 = mySum.myBind({ x: 1 });
let obj1 = new mySum3(1, 2);
console.log(obj1); // {a: 1, b: 2}
以下是对实现的分析:
- 前几步和之前的实现差不多,就不赘述了
- bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
- 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)
- 最后来说通过 new 的方式,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this
2.new 运算符原理以及实现
在调用 new 的过程中会发生以下四件事情:
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
根据以上几个过程,我们也可以试着来自己实现一个 new
// 先创建一个构造函数 M 和对应的实例 o1,下面会用于测试我们手写的 myNew 函数
var M = function (name) {
this.name = name;
};
var o1 = new M('o1');
var myNew = function (func) {
// 一个新对象被创建,它继承自 func.prototype
var o = Object.create(func.prototype);
// 构造函数foo被执行。执行的时候,相应的参数会被传入,同时上下文(this)会被指定为这个新实例
// new func 等同于 new func(),只能用在不传递任何参数的情况
var k = func.call(o);
if (typeof k === 'object') {
// 如果构造函数返回了一个对象,那么这个对象会取代整个new出来的结果
return k;
} else {
// 如果构造函数没有返回对象,那么 new 出来的结果就是之前 Object.create 创建的对象
return o;
}
};
var o2 = myNew(M);
console.log(o2 instanceof M); // true
console.log(o2 instanceof Object); // true
console.log(o2.__proto__.constructor === M); // true
console.log(o2.__proto__.constructor === Object); // false
M.prototype.walk = function () {
console.log('walk');
};
o2.walk(); // 'walk'
o1.walk(); // 'walk'
实现分析:
- 创建一个空对象,它继承自构造函数的原型
- 绑定 this 并执行构造函数
- 确保返回值为对象
3.instanceof 原理以及实现
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断 对象的原型链 中是不是能找到 类型的 prototype。
function myInstanceof(left, right) {
var prototype = right.prototype;
left = left.__proto__;
while (true) {
if (left === null || left === undefined) return false;
if (left === prototype) return true;
left = left.__proto__;
}
}
console.log(myInstanceof(o1, M)); // true
console.log(myInstanceof(o1, Object)); // true
console.log(myInstanceof([1, 2], Array)); // true
console.log(myInstanceof({ a: 1 }, Array)); // false
实现分析:
- 首先获取右边参数 类型的显式原型
- 然后获得左边参数 对象的隐式原型
- 然后一直循环判断 对象的隐式原型 是否等于 类型的显式原型,直到 对象的隐式原型为 null,因为原型链最终为 null
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。