likes
comments
collection
share

JS必知必会: 作用域 | 上下文 this | 闭包

作者站长头像
站长
· 阅读数 10

JS必知必会: 作用域 | 上下文 this | 闭包

JS必知必会: 作用域 | 上下文 this | 闭包

目标

  • 作用域、函数提升、变量提升、块级作用域、作用域链、变量查找规则、IFFE的概念及使用
  • 上下文 this
  • 闭包

知识要点

作用域

作用域(scope)就是变量的可访问范围,即作用域控制变量的可见性和生命周期

作用域链

JS必知必会: 作用域 | 上下文 this | 闭包

多层包裹 | 多层调用

let a = 'global'
console.log(a) 
function course(){
  let b = 'zhaowa'
  console.log(b)
  session()
  function session(){
    let c = 'this'
    console.log(c)
    teacher()
    function teacher(){
     	let d = 'yy'
      console.log(d)
      console.log('test1', b)
    }
  }
}
course()
  • 为什么函数调用和函数定义顺序可以颠倒? 函数提升 Hoist, 不用关注函数在哪里声明。
  • 变量会存在提升么? 变量提升 var -> 变量声明提升 var b = undefined
  • 内部可以取到外部的变量, 外部不能取到内部。作用域是向上查找的,冒泡查询。
  • 作用域查找 -> 函数提升 -> 变量提升 -> let 和 const 具有块级作用域, 实现隔离 -> 模块化(基础是函数)
  • IFFE 立即执行函数,可以用来实现作用域隔离

this 上下文 context

在JavaScript中,上下文(context)通常指的是函数执行时的环境,这包括函数内部的变量,this的值,以及其他与特定函数调用相关的值。当函数被调用时,会创建一个上下文,这个上下文定义了函数内部代码可以访问的变量,函数参数,以及this的值。

  1. this 是在执行时动态读取上下文决定的, 而不是创建时。
  2. 作用域链和上下文有什么区别? 对于编译器来说, 作用域链是创建态(静态分析), 上下文是执行态(执行的时候确定)
  3. 各个使用态中的指针指向:

函数中直接调用-this指向执行全局

function foo(){
  console.log("函数内部this", this)
}
foo(); // 指向全局的 window

const foo2 = function(){ console.log(this)}
foo2() // => window

;(function () {
  console.log(this)
})() // => window

隐式绑定

function fn(){
  console.log("隐式绑定", this)
}
const obj = {
  a: 1,
  fn
}
obj.fn = fn
obj.fn() // => obj  谁调用fn, this 就是谁。


const foo = {
  bar: 10,
  fn: function(){
    console.log(this.bar)
    console.log(this)
  }
}

let fn1 = foo.fn
fn1() // undefined , window
// 问题: 如何改变属性的指向? 三种方式

const o1 = {
  text: 'o1',
  fn: function(){
    console.log(this)
    return this.text
  }
}

const o2 = {
  text: 'o2',
  fn: function(){
    console.log(this)
    return o1.fn()
  }
}
const o3 = {
  text: 'o3',
  fn: function(){
    console.log(this)
    let fn = o1.fn
    return fn()
  }
}

console.log(o1.fn());// 'o1'
console.log(o2.fn());// 'o1'
console.log(o3.fn());// undefined   o3.fn() => 全局执行 fn() => window

显示绑定- bind & call & apply

call & apply & bind 的区别

相同点: 认为的改变this的指向

不同点:

  • call 和 apply 传参不同, call 上下文和参数列表 , apply 上下文和数组参数
  • bind 返回一个函数, call 和 apply 立即执行,返回结果
手写一个bind
// 1. 说明原理, 写下注释
// 2. 根据注释补全代码

// 需求: 手写 bind
// bind 挂载位置 => 方法 => Function.prototype
// 输入: arguments 第一项是 this (上下文), 第二项到最后一项为函数的传参
// 返回值: 函数, 内部构造一个函数 => 返回原函数的结果且传参继承
Function.prototype.newBind = function () {
  //  当前执行态的 this
  const _this = this

  // 伪数组 [Arguments] { '0': {}, '1': 1, '2': 2, '3': 3 }
  console.log(`arguments`, arguments)

  // 将伪数组转为数组
  const args = Array.prototype.slice.call(arguments)
  // const args = Array.from(arguments) // 另一种转换方法

  console.log(args)
  const context = args.shift() // 将栈顶(context) 推出
  return function () {
    return _this.newApply(context, args)
  }
}

Function.prototype.newApply = function (context, args = []) {
  // 边缘检测
  if (typeof this !== 'function') {
    throw new TypeError('TYPE ERROR')
  }
  context = context || window
  const key = Symbol()
  context[key] = this
  let result = context[key](...args) // foo 会在这里执行
  delete context[key] // [Symbol()] 销毁
  return result
}

function foo() {
  console.log(this) // { name: 'my', [Symbol()]: [Function: foo] }
  console.log(`foo`, Array.from(arguments))
}
foo.newBind({ name: 'my' }, 1, 2, 3, 4)()

闭包

任何帮助局部变量逃逸到外部的方式就可以称为是闭包。

补充知识点

什么是伪数组(类数组)? 伪数组如何转成数组。

指类似数组的对象,但是并不具备数组的方法。

常见的伪数组包括函数的 arguments 对象、DOM 元素集合(比如 document.getElementsByTagName() 返回的对象)、以及通过一些内置方法(比如 querySelectorAll)获取到的元素集合等。

伪数组转换为真正的数组有三种方法 1. Array.from 2. 展开运算符 [...pseudoArray] 3. Array.prototype.slice.call()

因为 slice() 方法是数组对象的一个方法,它会创建一个新的数组对象,并将原始数组(或者类数组对象)的一部分元素复制到新数组中。当您调用 slice() 方法时,JavaScript 引擎会检查调用该方法的对象是否是一个数组。如果调用该方法的对象不是数组,JavaScript 引擎会尝试将其转换为一个数组。而且,它会检查对象是否具有 length 属性,以确定要复制的元素数量。

转载自:https://juejin.cn/post/7353113367533338687
评论
请登录