1. 知识点补充:
首先在模拟实现前,先Mark一些我之前不知道的知识:
a. eval(string)函数:可计算某个字符串,并执行其中的JavaScript代码
其中,string是必需传入的待计算或待执行的语句,并且必须是原始字符串的形式!
eval(string)相当于<script> string </script>
b. 类数组对象(Array-like Object)
类数组对象是一个对象,比如:arguments、DOM API返回的NodeList对象都属于类数组对象,具有指向对象元素的数组index下标和length属性,但是它们不能使用push/pop/shift/unshift等数组方法!
但是如何能将类数组转换为真正的数组呢?有如下方法:
- Array.prototype.slice.call( arguments ) // 在低版本IE下不支持
- [].slice.call( arguments ) // 等同于1
- let arr = Array.from( arguments ) // ES6,可将类数组对象和可遍历对象转为真正的数组
- let arr = [ ...arguments ]
以下例为例演示:
var foo = { value: 1 }; function bar(name, age) { console.log(this.value);
console.log(name)
console.log(age) } bar.call(foo, 'ning', 20);
这里可以考虑将bar这个函数作为foo的一个方法,然后在外层执行这个函数,然后再删掉该函数即可!
2. call的模拟实现
Function.prototype.call2 = function (context) { context.fn = this; // context是foo,this是bar也就是调用call的那个函数 context.fn(); delete context.fn; } // 使用下例来验证call2是否可行 var foo = { value: 1 } function bar() { console.log(this.value); } bar.call2(foo);
content.fn = this;这句首先获取到了调用call的函数,本例这里也就是bar;
context.fn();即执行bar这个函数;
delete删掉该函数。
但是现在的模拟中有几个问题:
- 不能传入参数,因此我们将利用arguments,从Arguments对象中从第二个参数(因为第一个参数是this)开始取值,放到一个数组里,再把这个数组放到要执行的函数的参数里
- this参数传入null或者undefined时,我们需要将this指向window
- 当call2()内传的不是一个对象,而是一个基本数据类型时,如何处理?(在call实现时会自动调用Object()转换)
- 函数可以有返回值
所以我们得到以下call2()代码:
Function.prototype.call2 = function (context) { context = context ? Object(context) : window; context.fn = this; var arr = []; for (var i = 1, len = arguments.length; i < len; i++) { arr.push('arguments[' + i + ']'); } var result = eval('context.fn(' + arr + ')'); delete context.fn; return result; }
下面我们测试一下:
var value = 'global'; var foo = { value: 1 } function bar(name, age) { console.log(this.value) return { value: this.value, name: name, age: age } } bar.call2(null) // global console.log(bar.call2(foo, 'ning', 20)) // 1 // {value: 1, name: "ning", age: 20}
说明两点:
- arr.push('arguments['+ i +']');这句得到的是(2) ["arguments[1]", "arguments[2]"]一个新数组,是我们想要的
- eval('context.fn('+ arr +')');这句中arr会自动调用arr.toString()得到一个字符串:arguments[1],arguments[2],然后进行字符串拼接
下面给出ES6版本的:
Function.prototype.call2 = function (context) { context = context ? Object(context) : window; context.fn = this; let arr = [...arguments].slice(1); let result = context.fn(' + arr + '); delete context.fn; return result; }
3. apply的模拟实现:
Function.prototype.apply2 = function (context, arr) { context = context ? Object(context) : window; context.fn = this; var result = []; // 没有arr参数直接执行 if (!arr) { result = context.fn(); // 有arr参数则将参数拼接后执行 } else { var args = []; for (var i = 0; i < arr.length; i++) { args.push('arr[' + i + ']') } result = eval('context.fn(' + args + ')') } delete context.fn; return result; }
下面给出ES6版本的:
Function.prototype.apply2 = function (context, arr) { context = context ? Object(context) : window; context.fn = this; let result = []; if (!arr) { result = context.fn(); } else { // ...arr的使用 result = context.fn(...arr) } delete context.fn; return result; }