Open dongyuanxin opened 5 years ago
手动实现 call 的函数中使用了 context 的 fn 属性,如果传入的参数 context 中本来就有 fn 属性且调用函数 test 中使用到 fn 的话怕是会出错。比如 function test(arg1, arg2){ console.log(arg1,arg2); console.log(this.a, this.fn); } test.call2({ a: 'a', fn: 'b' }, 1, 2);
关于深拷贝那段,我在网上看到很多下面的代码,但是我不知道哪里有坑,用了楼主的示例结果也没问题。 function deepCopy(obj){ let result = Array.isArray(obj) ? [] : {}; for(let key in obj){ if(obj.hasOwnProperty(key)){ if(obj[key] && typeof obj[key] === 'object'){ result[key] = deepCopy(obj[key]); }else{ result[key] = obj[key]; } } } return result; }
@purain 手动实现 call 的函数中使用了 context 的 fn 属性,如果传入的参数 context 中本来就有 fn 属性且调用函数 test 中使用到 fn 的话怕是会出错。比如 function test(arg1, arg2){ console.log(arg1,arg2); console.log(this.a, this.fn); } test.call2({ a: 'a', fn: 'b' }, 1, 2);
这里确实有问题,输出是:
1 2
a function test(arg1, arg2){
console.log(arg1,arg2);
console.log(this.a, this.fn);
}
原因在于手动实现的call
修改了上下文fn
,进入函数打印,此时fn
不再是b
,而是函数本身。目前没有好的办法,可以将call2
的fn
字段换成更不常用的__fn__
,或者使用es6的Symbol
语法。
修正版:
Function.prototype.call2 = function(context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
context = context || window;
const fn = Symbol('fn');
context[fn] = this;
const args = [...arguments].slice(1);
const result = context[fn](...args);
delete context[fn];
return result;
};
它的没问题,而且实现的比我的精简多了。不过同样没考虑“循环引用”的问题。
@purain 关于深拷贝那段,我在网上看到很多下面的代码,但是我不知道哪里有坑,用了楼主的示例结果也没问题。 function deepCopy(obj){ let result = Array.isArray(obj) ? [] : {}; for(let key in obj){ if(obj.hasOwnProperty(key)){ if(obj[key] && typeof obj[key] === 'object'){ result[key] = deepCopy(obj[key]); }else{ result[key] = obj[key]; } } } return result; }
//deepClone
typeof null === 'object' //true
//deepClone typeof null === 'object' //true
如果obj[key] = null的话, obj[key] && typeof obj[key] === 'object 为false
实现call/apply方法还是有一些不足的。给context添加额外属性会造成调用方法时,如果涉及到例如console.log(this)
操作会输出额外属性。我查到的方法是将函数转换成字符串, 并将其中的this
替换为context
,然后使用eval
方法转换成新函数。但是这样又涉及到使用正则判断this
串是否为变量this
,以及闭包外层变量被引用或context与闭包内变量重名问题。较难实现。
另外Function.prototype.call
的参数1,若为空,null
,undefined
,则绑定上下文至Window
。若为基本类型例如1,则绑定至new Number(1)
,这里的context赋值我是用
context = arguments[0] instanceof Object ? arguments[0] : new arguments[0].__proto__.constructor(arguments[0]);
来实现的。
使用 ES5 实现的双向绑定有点小问题。功能上完全可以, 但是obj可以直接定义一个空对象。 get函数也有点小问题, value 没有定义, 这样的话想要访问 obj.value 的时候是会报错的。并且 get 与 set 一般都是用来创建伪属性的, 最好不要直接设置在真实属性上。 另外在 set 中, 不需要再去改变 input 的值了, 因为在输入的时候 input 的值就改变过了。 建议改成如下代码:
const obj = {};
// ...
Object.defineProperty(obj, 'value', {
get: () => {
return document.querySelector('input').value;
},
set: (newValue) => {
document.querySelector("#value").innerHTML = newValue;
}
})
前端面试中常考的源码实现:https://xin-tan.com/passages/2019-03-18-interview-js-code/