js作用域和闭包

129 阅读3分钟

一、this的不同应用场景,

如何取值(this取什么样的值,是在函数执行的时候确认,不是定义时确认)

a.作为普通函数
  function fn1() {
      console.log(this)
  }
  fn1() // window
  
b.使用call apply bind 
  fn1.call({ x: 100 }) // { x: 100 }
  const fn2 = fn1.bind({ x: 200 })
  fn2() // { x: 200 }
  
c.作为对象方法被调用
  const zhangsan = {
      name: '张三',
      sayHi() {
          // this 即当前对象
          console.log(this)
      },
      wait() {
          setTimeout(function() {
              // this === window
              console.log(this)
          })
      },
      waitAgain() {
         setTimeout(() => {
             // this即当前对象
             console.log(this)
         })
      }
  }
  
d.在class方法中调用
  class People {
      constructor(name) {
          this.name = name
          this.age = 20
      }
      sayHi() {
          console.log(this)
      }
  }
  const zhangsan = new People('张三')
  zhangsan.sayHi() //zhang对象
  
f.箭头函数
箭头函数没有自己的this,继承外层上下文绑定的this

1.箭头函数和普通函数区别

1.没有 this,不能改变 this 绑定
2.不能通过 new 调用,当然也没有原型(prototype)
3.不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
4.箭头函数虽然没有 this ,但是还是可以在内部使用 this5.this 的绑定取决于定义函数时的上下文环境
6.一旦函数调用,任何改变 this 的方法都无效
7.不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数

2.bind和call和apply的区别?

a.相同点 
1.bind,call,apply都可以改变this指向
b.不同点 
  1.call,apply的传参方式不同 apply传数组
  2.bind,call,apply的返回值不同 bind返回是函数
c.使用场景: 
1.call 判断数据类型,类数据转数组 
const array = [1,2,3,4]
const type = Object.prototype.toString.call(array)
console.log(type, 'type 1')
const arrayLike = {
    0:'name',
    1:'age',
    2:'gender',
    length: 3
}
const res = Array.prototype.slice.call(arrayLike)
console.log(res, 'res 2')
2.apply 获取数组最大值 
const array = [1,2,3,4,5]
const max = Math.max.apply(null, array);
const min = Math.min.apply(null, array);
console.log(max,min, 'max 1')
3.bind react类组件事件响应函数的绑定

3.如何实现new操作符

1.创建空对象
2.获取构造函数
3.对象的__proto__指向Constructor.prototype
4.用apply的方式把构造函数Constructorthis指向obj执行Constructor
5.根据ret的执行结果判断返回构造函数的返回对象还是新创建的空对象
const ObejectFactory = (...args) => {
    // 1.创建空对象
    const obj = {}
    // 2.获取构造函数
    const Constructor = [].shift.call(args)
    // 3.对象的__proto__指向Constructor.prototype
    obj.__proto__ = Constructor.prototype
    // 4.用apply的方式把构造函数Constructor的this指向obj执行Constructor
    const ret = Constructor.apply(obj, args)
    // 5.根据ret的执行结果判断返回构造函数的返回对象还是新创建的空对象
    return typeof ret === 'object' ? ret : obj
}

二、手写bind函数

1.将参数解析为数组
2.获取this(取出数组第一项,数组剩余的就是传递的参数)
3.当前函数赋值
4.返回一个函数
5.执行原函数,并返回结果

Function.prototype.bind = function() {
      // 将参数解析为数组
      const args = Array.prototype.slice.call(arguments)
      // 获取this(取出数组第一项,数组剩余的就是传递的参数)
      const t = args.shift()
      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.bind({ x:100 }, 10, 20, 30)
  const res = fn2()
  console.log(res)

三、作用域

作用域
a.全局作用域
b.函数作用域
c.块级作用域(ES6新增)
 if(true) {
     let x = 100
 }
 console.log(x) // 报错
 自由变量
 a.一个变量在当前作用域没有定义,但被使用了
 b.向上级作用域,一层一层依次寻找,直至找到为止
 c.如果到全局作用域都没有找到,则报错xx is not defined

屏幕快照 2022-04-03 上午9.06.01.png

四、闭包:有权访问另一个函数作用域中的变量的函数

表现:
  a.函数作为参数被传递(所有的自由变量的查找,是在函数定义的地方,向上级作用域查找不是在执行的地方)
  function print(fn) {
      const a = 200
      fn()
  }
  const a = 100
  function fn() {
      console.log(a)
  }
  print(fn)
  
  b.函数作为返回值被返回
    function create() {
        const a = 100
        return function() {
            console.log(a)
        }
    }
    let fn = create()
    const a = 200
    fn()
    
   场景
   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'))

35a85edf8db1cb1378e20a479cfd7c4693584b0f.jpeg