作用域与this

  1.作用域

  LHS与RHS:如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。 赋值操作符会导致 LHS 查询。=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。

  LHS 和 RHS 查询都会在当前执行作用域中开始,如果有需要(也就是说它们没有找到所 需的标识符),就会向上级作用域继续查找目标标识符,这样每次上升一级作用域(一层楼),最后抵达全局作用域(顶层),无论找到或没找到都将停止。 不成功的 RHS引用(如console.log未声明的值)会导致抛出 ReferenceError 异常。不成功的 LHS引用(给未声明的值赋值)会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用 LHS引用的目标作为标识符,或者抛出 ReferenceError 异常(严格模式下)。

  作用域是根据名称查找变量的一套规则,javascript的作用域为词法作用域,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它们进行查找。词法作用域的特征就是作用域的定义发生在代码的书写阶段。

  作用域链

  当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套,因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量, 或抵达最外层的作用域(也就是全局作用域)为止。如果将作用域比作气泡,那么每声明一个函数或创建一个块都会为其自身创建一个气泡,气泡的外部无法访问到气泡内部的变量,但是里层的气泡能够顺着作用域链向外访问外层的变量。

  由于气泡的外部无法访问到气泡内部的变量的机制,所以可以使用函数将变量包装起来达到隐藏变量的功能,匿名立即执行函数就非常合适。

  

var a = 1
(function() {
    var a = 2
    console.log(a) // 2
})()
console.log(a) // 1

以 (function 而不是 function 开头是非常重要的区别,函数会被当作函数表达式而不是一个标准的函数声明来处理。

  除了函数,使用with和catch语句也会生成代码块,let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let 为其声明的变量隐式地了所在的块作用域。

  

  2.this

  在javascript中this的机制则更类似于动态作用域,而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,动态作用域链是基于调用栈的,而不是代码中的作用域嵌套。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

 

  this 既不指向函数自身也不指向函数的词法作用域,this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

 

  当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

 

  要想知道this的指向,就要弄清楚函数的调用位置。

  

function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的调用位置
}
function bar() {
// 当前调用栈是 baz -> bar
// 因此,当前调用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的调用位置
}
function foo() {
// 当前调用栈是 baz -> bar -> foo
// 因此,当前调用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的调用位置

  2.1 判断this

   1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。

    var bar = new foo()

  2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。

    var bar = foo.call(obj2)

  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。

    var bar = obj1.foo()

  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。

    var bar = foo()

  5. 箭头函数执行时没有自己的this,箭头函数中的this为函数运行时所在上下文的this

   2.2 手写call、apply

  思路:在Function.proptotype中新增自己的call/apply方法,这样每个函数实例都会继承得到该方法;由于函数中this指向与调用栈有关,要改变函数的this指向特定对象,可通过在该对象上新增一个函数属性,然后调用对象上的函数。

  

Function.prototype.zcall = funciton(obj, ...agrs) {
  const f = Symbol('f') // 避免f属性覆盖obj中的属性
  obj.[f] = this // 在原型中this指向实例
  const result = obj[f](...agrs)
  delete obj[f]
  return result  
}

 

  

 

 

  

posted @ 2021-03-04 14:31  千昭。  阅读(129)  评论(0编辑  收藏  举报