JS面试题

 
  • 类型判断instanceof
  • instanceof回答的问题是new函数所创建的对象的整条【prototype】链中是否有构造它的函数的prototype属性指向的对象
    • new函数所创建的对象的整条【prototype】链中
    • 是否有一个对象
    • 这个对象是构造它的函数的prototype属性指向的对象
  • 原型和原型链
    • 原型关系
      • 每个class都有显式原型prototype
      • 每个实例都有隐式原型_proto_
      • 实例的_proto_指向对应class的 prototype
    • 基于原型的执行规则
      • 获取属性xialuo.name或执行方法xialuo.sayhi()时
      • 先在自身属性和方法中寻找
      • 如果找不到则自动去_proto_中去查找

1.作用域,自由变量

  • 作用域

    • 全局作用域

    • 函数作用域

      • 属于这个函数的全部变量都可以在整个函数的范围内部使用以及复用(事实上在嵌套的作用域中也可以使用)。
    • 块级作用域(es6新增)

      • let关键字可以将变量绑定到所在的任意作用域中(通常就是{}内部),也就是,let为其声明的变量隐式的劫持了所在的块作用域(简单说块作用域就是{})
        //es6块级作用域
        if(true){
           let x = 100
        }
        cosole.log(x)   //会报错
    
  • 自由变量

    • 一个变量在当前作用域没有定义,但是被使用了
    • 向上级作用域,一层一层一次寻找,直至找到为止
    • 如果到全局作用域都没有找到,则报错xx is undefined
  • 闭包

    • 作用域应用的特殊情况,有两种表现:

      • 函数作为参数被传递
      • 函数作为返回值被返回
      //函数作为返回值
      function create() {
          let a = 100;
          return function () {
              console.log(a);
          }
      }
      let fn = create();
      let a = 200;
      fn()//100
      
       // 函数作为参数
      function print() {
          let a = 200;
          fn();
      }
      let a = 100;
      function fn() {
          console.log(a);
      }
      print(fn)//100
      // 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
      // 不是在执行的地方!!!
      

2.this

《你不知道的JS》中this总结

  1. 默认绑定

    function(){
      console.log(this.a);
    }
    var a = 2;
    foo();//2  this指向全局对象window
    
  2. 隐式绑定

    function foo(){
      console.log(this.a);
    }
    var obj = {
      a:2,
      foo:foo
    };
    obj.foo();//2
    //当foo()被调用时,它的前边加上了对obj的引用,当函数引用上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
    //this指向调用当前方法/函数的对象
    
  3. 显示绑定(call,apply),硬绑定(bind)

    call(obj,参数1,参数2)
    apply(obj,[参数1,参数2]),可以用数组传递参数
    bind(obj,参数1,参数2……),返回一个硬编码函数
    function foo(){
      console.log(this.a);
    }
    var obj={
      a:2
    }
    foo.call(obj)//2
    //我们可以在调用foo时强制把它的this绑定到obj上
    
  4. new 绑定

    1. 创建/构造一个全新的对象
    2. 这个新对象会被执行【Prototype】链接
    3. 这个新对象会绑定到函数调用的this
    4. 如果函数没有返回其他对象,那么new 表达式中的函数会自动返回这个新对象
    function foo(a){
      this.a = a;
    }
    var bar = new foo(2);
    console.log(bar.a);//2
    
  • 作为普通函数

  • 使用call apply bind

    • 手写bind函数:

      // 模拟 bind
      Function.prototype.bind1 = function () {
          // 将参数拆解为数组
          const args = Array.prototype.slice.call(arguments);
      
          // 获取 this(数组第一项)
          const t = args.shift();
          // fn1.bind(...) 中的 fn1
          const self = this;
      
          // 返回一个函数
          return function () {
              return self.apply(t, args)
          }
      };
      
      function fn1(a, b, c) {
          console.log('this', this);
          console.log(a, b, c);
          return 'this is fn1'
      }
      
      const fn2 = fn1.bind1({x: 100}, 10, 20, 30);
      const res = fn2();
      console.log(res);
      
    • 原生bind函数

      function fn(a, b, c) {
          console.log('this', this);
          console.log(a, b, c);
          return 'this is fn1'
      }
      
      const fn2 = fn.bind({x: 100}, 10, 20, 30);
      const res = fn2();
      console.log(res);
      
  • 作为对象方法被调用

  • 在class方法中被调用

    • 指向当前实例本身
  • 箭头函数

    • 永远找它上级作用域的this来确定
  • this取值是在函数执行时确认的,不是在函数定义的时候确定的

    function fn1(){
      console.log(this);
    }
    fn1()//window
    fn1.call({x:100}) //{x:100}
    const fn2 = fn1.bind({x:200})
    fn2() //{x:200}
    
    屏幕快照 2021-04-03 上午11.07.22
//setTimeout中this指向问题也可以用bind()函数解决,其原理是bind()函数内部是用call或者apply实现的this显示绑定(改变this指向)
wait(){
  setTimeout(function(){
    console.log(this);
  }.bind(this))
}

3.实际可发中闭包的应用

  • 隐藏数据

  • 做一个简单的cache工具

    // 闭包隐藏数据,只提供 API
    function createCache() {
        const data = {}; // 闭包中的数据,被隐藏,不被外界访问
        return {
            set: function (key, val) {
                data[key] = val
            },
            get: function (key) {
                return data[key]
            }
        }
    }
    
    const c = createCache();
    c.set('a', 100);
    console.log( c.get('a') );
    
  • for循环中作用域和闭包问题

    • i是全局变量
    let i, a;
    for (i = 0; i < 10; i++) {
      a = document.createElement('a');
      a.innerHTML = i + '<br>';
      a.addEventListener('click', function (e) {
        e.preventDefault();
        alert(i)
      });
      document.body.appendChild(a)
    }
    //输出结果0 1 2 …… 9 的a标签,点击每个都是弹出10,原因是for循环是在点击事件发生前就已经执行完,此时i的值是10,又因为i定义的是全局变量,所以每次点击都是10
    
    • i是局部变量(块级作用域)
    let a;
    for (let i = 0; i < 10; i++) {
      a = document.createElement('a');
      a.innerHTML = i + '<br>';
      a.addEventListener('click', function (e) {
        e.preventDefault();
        alert(i)
      });
      document.body.appendChild(a)
    }
    //输出结果0 1 2 …… 9 的a标签,点击每个标签都会弹出对应的数值(0-9),原因是i被定义在局部作用域(块作用域),每次循环都形成一个新的块,那么每次都会给i赋予新的值(let 会把变量绑定到块作用域({})中)
    
    • 关于for循环setTimeout考察的循环次数细节问题

      for (var i = 0; i < 3; i++) {
        setTimeout(()=>{
          console.log(i);
          //输出3次3  当i=2时已经循环3次,当i==2循环结束后进行下一轮循环时,i++,此时i==3,i==3不符合条				件,所以终止循环,但是i还是会被加1等于3的,只不过等于3后下一轮不符合条件,所以不会打印
        })
      }
      
      for (var i = 0; i <= 3; i++) {
        setTimeout(()=>{
          console.log(i);
          //输出4次4   当i=3是(此时已经循环3次)符合条件继续循环(一共循环4次),循环结束后i++,i+1=4,			但是i==4十不符合条件,不会打印,就终止循环
        })
      }