Skip to content
Go back

javascript scope

Published:  at  13:14

On this page

根据名称查找变量的一套规则

作用域链

从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止

LHS & RHS

源于编译器的工作原理,用于描述变量查找的方式

LHS 查询

RHS 查询

function foo(a) {
  var b = a;
  return a + b;
}

var c = foo( 2 );
// 1. 找出所有的 LHS 查询(这里有 3 处!) c = ..;、a = 2(隐式变量分配)、b = ..
// 2. 找出所有的 RHS 查询(这里有 4 处!)foo(2..、= a;、a ..、.. b

有什么用? - 异常

如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常

function foo(a) {
  console.log(a + b) // Uncaught ReferenceError: b is not defined
}
foo(2)

执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量, 全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非 “严格模式”下。

function foo(a) {
  b = a;
}
foo(2);
console.log(b)
// 2
// undefined

严格模式

function foo(a) {
  'use strict'
  b = a
}
foo(2)
console.log(b)

https://stackoverflow.com/questions/11677452/possible-to-enable-strict-mode-in-firebug-and-chromes-console

解决:

function foo(a) {'use strict'; b = a } foo(2); console.log(b);

如果 RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作, 比如试图对一个非函数类型的值进行函数调用,或着引用 null 或 undefined 类型的值中的 属性,那么引擎会抛出另外一种类型的异常,叫作 TypeError

function foo(a) {
  a()
}

foo(2)

总结

ReferenceError:The ReferenceError object represents an error when a variable that doesn’t exist (or hasn’t yet been initialized) in the current scope is referenced.

哪里出现过:The directive does not have the attribute value which is valid as LHS.

词法作用域

静态的

词法作用域就是定义在词法阶段的作用域,换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)

例外情况

eval

var b = 2

function foo(str, a) {
  eval(str) // 欺骗!
  console.log(a, b)
}

foo('var b = 3;', 1) // 1, 3
var b = 2

function foo(str, a) {
  // eval(str) // 欺骗!
  {
    let b = 6
  }

  console.log(a, b)
}

foo('let b = 3;', 1) // 1, 2

eval 引入了新的代码块

with

function f(a, b) {
  with (b) {
    console.log(a)
  }
}

//先在指定的对象中查找
f(1, { a: 2 }) //2
f(1, 2) //1

哪里用到 with


const app = new Vue({
  el: '#app',
  data: {
    count: 999,
  },
})

console.log(app.$options.render)

// function anonymous() {
//   with (this) {
//     return _c('div', { attrs: { id: 'app' } }, [_v('\n      ' + _s(count) + '\n      '), _c('span', [_v(' ' + _s(count + 1) + ' '])])
//   }
// }
// _c:h
// _v:创建文本节点

利:with 语句可以在不造成性能损失的情況下,减少变量的长度。其造成的附加计算量很少。使用 ‘with’ 可以减少不必要的指针路径解析运算。需要注意的是,很多情況下,也可以不使用 with 语句,而是使用一个临时变量来保存指针,来达到同样的效果。

弊:with 语句使得程序在查找变量值时,都是先在指定的对象中查找。所以那些本来不是这个对象的属性的变量,查找起来将会很慢。如果是在对性能要求较高的场合,‘with’ 下面的 statement 语句中的变量,只应该包含这个指定对象的属性

推荐的替代方案是声明一个临时变量来承载你所需要的属性

函数作用域

用 var 声明的变量,自动提升到函数作用域顶部

属于这个函数的全部变量都可以在整个函数的范围内使用及复 用(事实上在嵌套的作用域中也可以使用)。这种设计方案是非常有用的,能充分利用 JavaScript 变量可以根据需要改变值类型的“动态”特性。


function fun(x, f) {
  // debugger
  var x;
  var y = x;
  f = () => x
  x = 2;
  console.log(x, y, f())
}

fun(1)
var x // 没有右值,只有静态语音,没有运行时语义
var x = undefined // 都有
function fun(x, f = () => x) {
  var x;
  var y = x;
  x = 2;
  console.log(x, y, f())
}

fun(1)
function fun(x, f = () => x) {
  // var x;
  var y = 'a';
  x = 2;
  z = 'z'
  console.log(x, y, f())
}

fun(1)

x 在 参数作用域找到了,不会挂到 window

块级作用域

代码块{}

使用 const,let 声明的变量,范围是块级作用域

for

if (true) {
  let a = 1
}

console.log(a)
try {
  console.log(a)
} catch (e) {
  console.log('catch', e)
}

console.log('out', e)

hoisting

console.log(a)
//...
var a = 1
var a;
console.log(a)
//...
a = 1

let 提升吗

console.log(a)
let a;

总结

  1. let 的「声明」过程被提升了,但是初始化没有提升。
  2. var 的「声明」和「初始化」都被提升了。
  3. function 的「创建」「初始化」和「赋值」都被提升了。

执行上下文

this + scope

动态的调用者 + 静态的被调用者

this 的指向

this 的值是在执行的时候才能确认,定义的时候不能确认

直接调用

this 指向 window

function foo() {
  console.log(this.a) //1
}

var a = 1
foo()

间接调用

谁调用了函数,谁就是 this

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

var obj = {
  fn: fn,
}

obj.fn() //this->obj

类的实例

在构造函数模式中,类中 (函数体中) 出现的 this.xxx=xxx 中的 this 是当前类的一个实例

function CreateJsPerson(name, age) {
  //this是当前类的一个实例p1
  this.name = name //=>p1.name=name
  this.age = age //=>p1.age=age
}

var p1 = new CreateJsPerson('abc', 48)

箭头函数

箭头函数没有自己的 this,看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 是 window

let btn1 = document.getElementById('btn1')
let obj = {
  name: 'kobe',
  age: 39,
  getName: function() {
    btn1.onclick = () => {
      console.log(this) //obj
    }
  },
}

obj.getName()

改变作用域

call、apply 和 bind:this 是第一个参数

function add(c, d) {
  console.log(this.a + this.b + c + d)
}

var a = 10
var b = 20
var o = {
  a: 1,
  b: 3,
}

add(30, 40) // 10 + 20 + 30 + 40 = 100
add.call(o, 5, 7) // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]) // 1 + 3 + 10 + 20 = 34
add.bind(o)(1, 2) // 1 + 3 + 1 + 2 = 7

(0, function)(param)

逗号运算符,对它的每个操作数求值(从左到右),并返回最后一个操作数的值

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

const obj = {
 fun: function () {
  return this;
 },
};

console.log(1, obj.fun());
console.log(2, (0, obj.fun)());

(0, obj.fun)() 等价与  fun = obj.fun; fun()

寻找 this

最佳实践


Suggest Changes

Previous Post
flutter
Next Post
JavaScript engine and runtime

Most Related Posts

  • css

    Published:  at  20:39

    css

  • low code

    Published:  at  20:30

    low code

  • ES6 基础

    Published:  at  12:40

    这篇博客主要介绍了ES6的基础特性,包括let/const声明、变量解构赋值等新语法的使用方法和注意事项

  • codec in js

    Published:  at  17:00

    codec in js

  • dart

    Published:  at  13:03

    dart