dongyuanxin / blog

📚 专注Web与算法
https://0x98k.com
MIT License
1.38k stars 184 forks source link

「评论」前端面试中常考的源码实现 #57

Open dongyuanxin opened 5 years ago

dongyuanxin commented 5 years ago

前端面试中常考的源码实现:https://xin-tan.com/passages/2019-03-18-interview-js-code/

purain commented 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);

purain commented 5 years ago

关于深拷贝那段,我在网上看到很多下面的代码,但是我不知道哪里有坑,用了楼主的示例结果也没问题。 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; }

dongyuanxin commented 5 years ago

@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,而是函数本身。目前没有好的办法,可以将call2fn字段换成更不常用的__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;
};
dongyuanxin commented 5 years ago

它的没问题,而且实现的比我的精简多了。不过同样没考虑“循环引用”的问题。

@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; }

yanxj0 commented 5 years ago
//deepClone
typeof null === 'object'  //true
zhou-z-xin commented 5 years ago
//deepClone
typeof null === 'object'  //true

如果obj[key] = null的话, obj[key] && typeof obj[key] === 'object 为false

kingyaroglek commented 4 years ago

实现call/apply方法还是有一些不足的。给context添加额外属性会造成调用方法时,如果涉及到例如console.log(this)操作会输出额外属性。我查到的方法是将函数转换成字符串, 并将其中的this替换为context,然后使用eval方法转换成新函数。但是这样又涉及到使用正则判断this串是否为变量this,以及闭包外层变量被引用或context与闭包内变量重名问题。较难实现。

另外Function.prototype.call的参数1,若为空,nullundefined,则绑定上下文至Window。若为基本类型例如1,则绑定至new Number(1),这里的context赋值我是用

context = arguments[0] instanceof Object ? arguments[0] : new arguments[0].__proto__.constructor(arguments[0]);

来实现的。

xdliyushen commented 4 years ago

使用 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;
            }
})