深入理解JavaScript之1
1.深入理解JavaScript的变量和作用域机制
//JavaScript的变量声明机制
//词法作用域
/*
词法变量的作用域查找机制:先在当前的作用域中查找--->逐级往最外层的作用域中查找
一旦找到,则立刻停止查找;;所以会出现隐式遮蔽效应;;
这说明词法作用域在代码书写期间函数声明的位置就被已经固定;如果想要在运行时动态
修改词法作用域,则可以通过eval和with函数机制:
eval机制:可以将输入的代码字符串进行字符替换并执行;;这意味着可以将一个变量
声明的代码串传入,从而实现运行时修改变量的词法作用域,如以下代码:运行结果为
1 3;
with机制:with(obj){
a=3
}完全声明了一个obj的作用域
副作用:如果进行LHS查找时,没有在所有区域查找成功,会自动在全局作用域
自动创建一个a变量,造成全局变量的泄漏!其实不仅仅是with,包括所用var
变量如果LHS没有查找到都会自动创建一个全局变量,造成泄漏!所以在声明时务必
加上var或者let将变量声明锁定在当前作用域,防止变量的作用域泄漏!
with(obj){
var a=3
}
缺点:由于无法知道所传入的代码具体内容,造成引擎无法在编译时对作用域查找进行优化,所有的
优化全部失效,造成变量的作用域查找效率变慢,从而导致代码执行也变慢!!
*/
function foo(str,a){
eval(str)
console.log(a,b)
}
var b=2
foo("var b=3",1)
//函数及块作用域
/*
认清:标识符和作用域气泡;;哪些标识符属于哪个气泡!
注意函数表达式和函数声明的区别,函数声明是以function ...开始,而函数表达式
是以(function ...)(...)或者foo(...)开始
二者的区别在于:函数声明将被绑定在所在当前的作用域,可以在当前作用域进行调用
而函数表达式被绑定在表达式自身的函数中而非当前的所在的作用域,也就是说只能被就地
代码执行,不能再当前作用域的其他地方被调用访问!!!
let const var的变量声明,let表示声明在块作用域,const也是块作用域,但是在声明是必须初始化
而且不能被改变,而var声明表示当前的作用域而非块作用域内
JS引擎在预编译时会自动优化进行变量声明,并将绑定在相应的作用域中,当执行调用代码时,所使用的变量
将优先在所在绑定的作用域中查找!!如果当前声明但是未初始化就会undefined,如果当前没有声明,
就会在全局作用域查找,未找到就会is not defined!!千万别被JS执行为一行一行执行给蒙蔽了!!
如下:输出的结果为1
原因在于function a(){} JS引擎在预编译是自动在b气泡作用域中声明了一个var a=function(){};;
从而导致全局变量a被屏蔽
由于JS引擎的这种预编译的特点,导致变量提升,即对于函数可以先声明再调用,也可先调用再声明,对于
变量如果先调用再声明,则会出现变量提升的情况;
*/
var a = 1;
function b() {
a = 10;
function a() {}
}
b()
console.log(a)
{
console.log(d)
let d="let"
}//输出is not defined,说明预处理绑定在{}块作用域内!
{
console.log(d)
var d="let"
}//输出undefined,说明预处理绑定在{}匿名对象作用域上!
for(let i=0;i<10;++i){
console.log(i)
}
console.log(i)//Reference Error
//相当于
{
let i;
for(i=0;i<10;++i){
console.log(i)
}
}
console.log(i)
注意1:C++与JavaScript的变量使用的区别
-
区别一:JavaScript变量的使用大部分都被隐式解释,而C++的使用都是显式的;;如在JavaScript中,基本类型都是按照值的形式进行使用即直接操作保存在变量中的实际值,JavaScript的基本类型为undefined null number string boolean(特点:五种声明的变量保存在栈区,复制操作会创建一个 基本类型值的副本,不能给基本类型添加属性)
JavaScript的复杂类型都是隐式的引用访问:复杂类型有,object array function(特点:不能直接访问内存,操作对象时操作的是对象的引用,而不是实际的对象值,保存在堆区中,保存对象的变量实际是指向的指针,进行复制操作时实际复制的是指针,可以为对象添加属性和方法)
**注意2:**函数调用时type error和reference error的区别
- 前者表示在作用域找到了,但是无法执行;后者表示不能再作用域找到
//由于函数和变量声明都会进行提升
foo()//type error
bar()//reference error
var foo=function bar(){...}
//相当于
var foo()
foo()
bar()
foo=function(){ var bar=...}
//注:对于函数声明会被提升,但是对于函数表达式不会出现变量提升,此外,对于函数声明会被赋值,但是
//对于函数表达式却不会被复制
foo()
function foo(){...}//成功调用!
var foo=function(){...}//type error!
**注意3:**函数声明和函数表达式的区别
-
js引擎会对js代码所有的变量及函数声明先进行预编译,并绑定在相应的作用域,而对于代码中的执行代码,只有解释器执行到此处才会开始执行!
-
也就是说解释器对函数声明和函数表达式的处理方式是不同的,对于函数声明由于执行之前已经经过预编译处理,并绑定在了相应的作用域中,而函数表达式相当于执行代码,只有在执行到此处代码语句时才会执行!
-
特点二:函数声明,其函数名称是必须的,函数名称相当于指向函数对象的指针,以后的函数调用以及赋值操作都必须通过这个函数名称;;;而对于函数表达式,函数名称是可选的,此外如果有函数名称,此变量相当于函数内部的一个局部变量
var sub = function(a1,a2){
return a1-a2;
}
var sub = function f(a1,a2){
return a1-a2;
}
console.log(f(5,3)); //错误调用方式
console.log(sub(5,3)); //正确调用方式
//相当于
var sub=function(a1,a2){
var f=...
}
**注意4:**对于声明提升,如果变量和函数名称相同,则函数声明优先被提升
foo()//1而非2
var foo
function foo(){
console.log(1)
}
foo=function(){
console.log(2)
}