1.LHS和RHS
概念定义:LHS和RHS的含义是‘赋值操作的左侧或者右侧’,并不意味着就是“=”赋值操作符的左侧或者右侧。赋值操作符还有其他几种形式,因此在概念上最好理解为“赋值操作的目标是谁(LHS)”和“谁是赋值操作的源头(RHS)”。
function foo(a){
console.log(a)
}
foo(2)//此处存在隐式的赋值操作,a=2
在上述代码中:
- 存在隐式的赋值操作,即foo中的a=2,此时会进行LHS查找,观察是否已经声明了a这个变量,如果存在则进行赋值操作。(赋值操作)
- 在console.log操作中会对a进行RHS查找,观察是否在之前就对a进行过赋值操作,取到a的值。(取值操作)
测试题:中出其中所有的LHS查询(3处)和RHS查询(4处)。
function foo(a){
var b = a
return a + b
}
var c = foo(2)
LHS查询:c =..., a=2, b=...
RHS查询:...foo(), =a, a, b
2.词法作用域
无论函数在哪里被调用,也无论他如何被调用,他的词法作用域都只由函数被声明的位置决定。(要在声明的位置去找)
function bar(){
console.log(myName)
}
function foo(){
var myName = "qqq"
bar()
}
var myName = 'ppp'
foo()//这里会打印‘ppp’,因为从bar找myName找不到,就从bar声明的地方向外寻找,找到myName = 'ppp'
稍微复杂一点的。(需要好好寻找作用域链)
var bar = {
myName:"oooo",
printName:function(){
console.log(myName)
}//printName是在变量中声明的而不是函数中,因此他的词法作用域是全局作用域
}
function foo(){
let myName = 'ppp'
return bar.printName
}
let myName = 'llll'
let _printName = foo() //这个时候的_printName是一个在全局作用域中声明的崭新的函数。
_printName() //根据作用域链找到llll
bar.printName()//根据作用域链找到llll
“欺骗”词法作用域的方法:eval(),with()。(这两种方法非严格模式下可以正常使用,在严格模式下会有影响,with()被完全禁止,在保留核心功能的前提下,间接或者非安全的使用eval()也被禁止了。)
- eval():eval():接收一个字符串作为参数,参数中的内容会被视为在eval()所在的位置进行执行。
function foo(str,a){
eval(str)//相当于在此处执行了b=3的操作。因此在输出的时候,b是3而不是2。
console.log(a,b)
}
var b = 2;
foo('b=3',5) //5,3
2.with()
function foo(obj){
with(obj){
a = 2
}
}
var o1 = {
a:1
}
var o2 = {
b:1
}
foo(o1)
console.log(o1.a)//2
foo(o2)
console.log(o2.a)//undefined
console.log(window.a)//a
上述代码输出解释:
- 当我们传递o1给with的时候,with所声明的作用域是o1,而这个作用域中含有一个同o1.a的标识符,所以修改o1中a的值。
- 当我们传递o2给with的时候,将o2作为作用域,其中并没有a标识符,因此进行了正常的LHS标识符查找。(o2中没有,就去全局中寻找,没有寻找到,就自动创建了一个全局变量。)
3. 函数作用域
立即执行函数表达式(LIFE/Immediately Invoked Function Expression)
第一种写法
(function(){...}())
第二种写法
(function(){...})()
区别在于后面的括号有没有括进去,两种写法功能一致。
4. 块作用域
块级作用域的例子:
- try/catch
try{
undefined()
}
catch(err){
console.log(err)//这里会正确的输出,catch创建了一个块级作用域,其中声明的部分只在catch中有效
}
console.log(err)//这里会报错,因为没有err
2.let/const
- let和var的区别中有:let存在块级作用域,而var没有,var存在变量提升。
//对于var
var foo = true,baz = 10;
if(foo){
var bar = 3
}
console.log('bar',bar)//这里会正确打印bar的值,bar,3。var会进行变量提升,然后赋值,就导致能正确的打印出bar的值。
if(baz > bar){
console.log('baz',baz)
}
//对于let
var foo = true,baz = 10;
if(foo){
let bar = 3
}
console.log('bar',bar)//这里会报错,就是因为let的块级作用域。ReferenceError: bar is not defined
if(baz > bar){
console.log('baz',baz)
}
- const同let一样存在块级作用域,区别在于const声明的时候必须赋值,同时赋值之后不能进行修改。
const b = 4
b=5 //这里就会报错。TypeError: Assignment to constant variable.
console.log('b',b)
块作用域的替代方案
{
let a = 2
console.log(a) //2
}
console.log(a) //ReferenceError: a is not defined
要实现这种效果可以使用try/catch
try{
throw 2
}catch(a){
console.log(a) //2
}
console.log(a) //ReferenceError: a is not defined
5. 提升
造成提升的原因:在代码执行之前会先进行编译,在编译的时候包括变量和函数的声明都会在执行之前首先被处理。(只有声明本身被提升,赋值或者其他逻辑操作会留在原来的位置。)
var a = 2
上面的代码可以被看做分成两部执行
var a //在编译的时候执行
a = 2 //在正常的赋值阶段执行。
因此分析下面的问题
示例问题:
a = 2
var a
console.log(a) // 2
实际上是相当于
var a //编译阶段
a = 2 //执行阶段
console.log(a)
注意点:
- 函数声明会被提升,函数表达式不会提升。
//函数声明
foo()
function foo(){
console.log("执行函数")
}
//函数表达式
bar()
var bar = function foo(){
console.log("执行函数")
}
//会报错:TypeError: bar is not a function。
//可以看到报错信息是TypeError,而并不是ReferenceError,这是因为在执行bar()的时候,bar的值是undefined
//上述代码可以分解成
var bar
bar()//所以这里不能当做函数执行。
bar = function foo(){
console.log("执行函数")
}
- 函数会被首先被提升,然后才是变量。
foo() //function
var foo
function foo(){
console.log("function")
}
foo = function(){
console.log('variable')
}
5. 闭包
简单了解闭包问题。 juejin.cn/post/717877…