前端面试复习1---this/闭包/作用域

this/闭包/作用域

program
静态创建
作用域链的确定:当前所有变量和所有父级变量
变量声明:变量,函数,参数
动态执行
this/context/指针
变量使用
函数引用

一、作用域

  • 作用域可以理解为:变量的作用域(变量作用的范围)
  • 在 js 中,对象和函数同样也是变量。

1. 作用域类型

  1. 局部作用域:函数作用域、块级作用域
  2. 全局作用域:<script></script>标签、js 文件

2. 变量、函数提升

  1. var 的声明有三步:创建,初始化,赋值。不管 var 声明在哪一行,预解析时它的前两步创建和初始化都会被提升到作用域的顶端。
  2. function 与 var 区别就是,function 声明的 创建,初始化,赋值三步,在预解析时都会被提升到作用域的顶端。
  3. let const 的声明在那行,这行到作用域的顶端就会形成临时死域。
  4. let 和 var 声明一样有三部:创建,初始化,赋值。不一样的是 let 声明只有创建被提升,所以会产生临时死域。
  5. const 和 let 只有一个区别,就是 const 声明只有两步:创建,初始化两步,没有赋值过程。

临时死域示例:

{
  // 作用域顶端 x 的临时死域--start

  console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization

  // x 的临时死域--end
  let x = 1
}

开始的时候我简单的认为 let const 是没有变量提升的,直到我看到了下面这段代码和方应杭老师的这篇博文 我用了两个月的时间才理解 let

x = "global";
// function scope:
(function() {
    x; // not "global"

    var/let/… x;
}());
// block scope (not for `var`s):
{
    x; // not "global"

    let/const/… x;
}

代码解读

x = 'global'
// 函数作用域
;(function () {
  console.log(x)
  var x = 1
})()
  1. console.log(x) 打印出来的不是 global,而是 undefined
  2. 说明 var 声明的 x 已经被提升了,如果没有被提升应该会顺着作用域链向上查找到 x = “global”
x = 'global'
// 函数作用域
;(function () {
  console.log(x)
  let x = 1
})()
  1. console.log(x) 打印出来的不是 global,而是控制台报错 Uncaught ReferenceError: Cannot access 'x' before initialization
  2. 说明 let 声明的 x 已经被提升了,只是存在临时死域,如果没有被提升应该会顺着作用域链向上查找到 x = “global”
x = 'global'

// 块级作用域
{
  console.log(x)
  const x = 1
}
  1. const 和 let 都是控制台报错 Uncaught ReferenceError: Cannot access 'x' before initialization
  2. 说明 const 和 let 声明的 x 已经被提升了,只是存在临时死域,如果没有被提升应该会顺着作用域链向上查找到 x = “global”

3. 作用域链

  1. 全局作用域链:由下往上找, 往上找不到再往下
<script>
    var aaa = 'aaa';
</script>

<script>
    console.log('我是上一个<script>标签中的:' + aaa); // 我是上一个<script>标签中的:aaa
</script>
  1. 局部作用域链:由里往外
var a = 1
function fn1() {
  console.log(a) // undefined
  var a = 2
}
fn1()
console.log(a) // 1
/* --
第一步:预解析,找 var function 参数 放到仓库
a = undefined;
fn1 = function fn1() {
    console.log(a);
    var a = 2;
}

第二步:逐行执行代码
表达式:a = 1;
函数调用:fn1() 执行函数调用的时候又开始了一个函数的作用域
函数里面的第一步预解析 a = undefined;
函数里面的第二步逐行执行代码 console.log(a);
console.log(a) 打印出 undefined 说明了是先找了函数内的变量 a
-- */
var a = 1
function fn1() {
  console.log(a) // 1
  a = 2
}
fn1()
console.log(a) // 2

/* --
第一步:预解析,找var function 参数 放到仓库
a = undefined;
fn1 = function fn1() {
    console.log(a);
    a = 2;
}

第二步:逐行执行代码
表达式:a = 1;
函数调用:fn1();执行函数调用的时候又开始了一个函数的作用域
函数 fn1 里面没有 var function 所以不用预解析
直接开始执行console.log(a);
console.log(a) 的时候在 fn1 预解析的小仓库中找不 a,js解析器会顺着 作用域链 找到上一级的 a = 1
-- */

二、this

1.函数的直接调用

function fn() {
  console.log(this) // Window
}
fn()

2. 隐式绑定

const obj = {
  fn() {
    console.log(this)
  },
}
obj.fn()

3. 显示绑定

call apply bind
const obj1 = {
  name: '我是obj1',
  fn() {
    console.log(this.name)
  },
}
const obj2 = {
  name: '我是obj2',
}

obj1.fn.call(obj2)
obj1.fn.apply(obj2)
obj1.fn.bind(obj2)()

// 打印结果都是 我是obj2
手写 call
  1. call 挂载在哪里:Function.prototype
  2. call 是什么:改变运行上下文,入参:新的 this + 原函数的参数
Function.prototype.call1 = function (newThis, ...params) {
  const fn = this // 原函数,即调用了 call1 的函数
  newThis.fn = fn // 改变运行上下文
  newThis.fn(...params)
  delete newThis.fn
}
手写 apply
Function.prototype.apply1 = function (newThis, params) {
  const fn = this
  newThis.fn = fn
  newThis.fn(...params)
  delete newThis.fn
}
手写 bind
  1. bind 挂载在哪里:Function.prototype
  2. bind 是什么: 改变运行上下文,入参:新的 this + 原函数的参数
  3. bind 需要什么:返回一个可执行函数(上下文被改变的原函数)
Function.prototype.bind1 = function (newThis, ...params) {
  const fn = this
  newThis.fn = fn
  return function (...params2) {
    newThis.fn(...params, ...params2)
    delete newThis.fn
  }
}

Function.prototype.bind2 = function (newThis, ...params) {
  const fn = this
  return function (...params2) {
    fn.call(newThis, ...params, ...params2)
  }
}

const obj1 = {
  name: '我是obj1',
  fn(a, b) {
    console.log(this.name, a + b)
  },
}
const obj2 = {
  name: '我是obj2',
}

obj1.fn.apply1(obj2, [1, 2]) // 我是obj2 3
obj1.fn.call1(obj2, 1, 2) // 我是obj2 3
obj1.fn.bind1(obj2, 1, 2)() // 我是obj2 3
obj1.fn.bind2(obj2, 1)(2) // 我是obj2 3

4. new - this 指向 new 出来的的实例对象

class testClass {
  constructor(name) {
    this.name = name
  }
  fn() {
    console.log(`我是${this.name}`)
  }
}
const obj1 = new testClass('obj1')
const obj2 = new testClass('obj2')
obj1.fn() // 我是obj1
obj2.fn() // 我是obj2
类中异步方法,this 有区别吗?
class testClass {
  constructor(name) {
    this.name = name
  }

  fn() {
    console.log(`我是${this.name}`)
    setTimeout(function () {
      console.log(this) // Window
    }, 100)
  }
}

const obj1 = new testClass('obj1')
obj1.fn()

三、闭包

一、什么是闭包

一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

上面这段话来自 MDN,有点抽象,可以先不管,大概知道就行。

解读一下就是:闭包是一个组合:一个函数 + 周围状态的引用

二、一句话两个表现

先记住一句话就行:闭包最常见的两个表现就是,函数作为参数被传递,函数作为返回值被返回

表现一:函数作为参数被传递
function aFn(fn) {
  const aaa = 100,
    bbb = 200
  fn()
}

const aaa = 0,
  bbb = 0
function bFn() {
  console.log(aaa, bbb) // 0 0
}
aFn(bFn)
表现二:函数作为返回值被返回
function cFn(fn) {
  const ccc = 100,
    ddd = 200
  return function () {
    console.log(ccc, ddd) // 100 200
  }
}

const ccc = 0,
  ddd = 0
const dFn = cFn()
dFn()

三、帮助理解的小提示

  1. 闭包实际上变量作用域应用的特殊情况。

  2. 闭包自由变量的查找,是在函数定义的地方向上级作用域查找,而不是在执行的地方向上级查找。

  3. 自由变量:当前作用域未定义的变量。

  4. 函数执行的上下文,不是本身的词法作用域。

    function a() {
      const c = 'ccc'
      return function () {
        console.log(c)
      }
    }
    const b = a()
    b() // b 函数的执行上下文是 window,但是它的本身的词法环境在它定义的地方,即 a 函数 return 的 function
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值