什么是作用域?
三大角色:引擎,编译器,作用域
- 引擎:负责JS的编译和执行——大佬
- 编译器:负责语法分析和代码生成——二哥
- 作用域:对声明变量查询,确定标识符的访问权限—三哥
示例:
var a =2; /// var a; a=2;可以看成是两步的操作:
1. 变量的赋值会执行两个动作,首先编译器会在当前作用域中声明一个变量()
2. 然后在运行时引擎会在作用域中查找该变量,如果找到会对它赋值
赋值操作的目标是谁LHS。LHS(left)和RHS(right)怎么理解?
谁是赋值操作的源头RHS。
被赋值的为LHS,主动调用的为RHS
对变量进行赋值,用LHS查询;目的是获取变量的值,用RHS查询
当一个块或者函数嵌套另一个块或者函数中时。作用域嵌套?
如果在当前作用域中找不到会一直往上层寻找
词法作用域:
立即执行函数表达式:
var a=2; (function foo(){ var a=3; console.log(a); //3 })(); console.log(a); //2
倒置代码的顺序
var a=2; (function IFIE(def){ def(window) })(function def(global){ var a=3; console.log(a); //3 console.log(global.a); //获得window的a的值 });
with—块作用域
try catch—
let 声明局部作用域,限定到作用域内才能执行
函数变量不会声明提前
const 创建局部作用域变量,值为固定的值无法修改
作用域提升:
1.变量作用域提升
先有鸡还是先有蛋?
结论:先有蛋(声明)后有鸡(赋值)
console.log(a); //undefined var a=2; =>等同于 var a; console.log(a); //undefined a=2;
2.函数作用域提升
foo(); function foo(){ /// console.log("hello world"); } =>正常执行并输出hello world foo(); var foo=function(){ /// console.log('hello world'); } =>输出错误,等同于↓ var foo; foo(); foo=function(){ console.log('hello world'); }
只有以function声明的变量才能提升,以变量声明的函数不能提升。
3.当声明多个相同函数的时候。
foo(); //2 var foo; function foo(){ console.log(1); } function foo(){ console.log(2); } =>等同于下面 foo=function(){ console.log(2); } function foo(){ console.log(1); } foo(); //谁最后加载,谁就最后执行
如果存在用变量声明的函数呢?
foo(); //3 function foo(){ console.log(1); } foo=function(){ console.log(2); } function foo(){ console.log(3); } =====>等同于下面 function foo(){ console.log(1); } function foo(){ console.log(3); } foo(); //3 foo=function(){ //变量声明无法提前 console.log(2); }
4.函数在块级里面的声明都不会提前
foo(); //error var a=true; if(a){ function foo(){console.log('a')}; }else{ function foo(){console.log('b')}; } ==>
总结:
1.我们总是习惯吧var a=2,看作是一个声明
然而JS引擎却是吧var a 和 a=2 两个单独声明,
第一个是编译阶段的任务,第二个是执行阶段的任务
2.什么是提升?
- 作用域声明出现在什么地方,都将在代码本身被执行前首先进行处理。
- 官方理解:所有的声明(变量和函数)都会被移动到各自作用于的最顶端
- 个人理解:称之为编译的声明(变量和函数)都将在执行之前提升至最顶端。
———————————- 华丽的分割线 ————————————–
作用域的闭包?
1.闭包无处不在….
闭包是发生在定义时的,但并不明显。
2.循环和比闭包:
(1)
for(var i=1;i<5;i++){ setTimeout(function timer(){ console.log(i); //这个i是不能访问i的 },i*1000); //这个i是可以访问循环的,因为它暴露在for循环里面 }延迟函数的回调会在循环结束时才执行。
根据作用域的工作原理,循环中的函数是各个迭代中分别定义的,都封闭在一个共享的全局作用域中。
个人理解:
其实就是定义了6个函数,然后在执行的时候,它的函数作用域里面的i会自动指向全局的i,也就是最后一个循环产生的6,至于为什么计算时间的i不是全局的,只能说明他是for循环里面产生的,并存储在里面。
(2)解决方案1。通过IIFE(即时执行)解决
for(var i=1;i<=5;i++){ (function(){ setTimeout(function timer(){ console.log(i); },i*1000); })(); } ///这样写还是全部输出6
因为没有存储外部作用域i的东西
for(var i=1;i<=5;i++){ (function(){ var j=i; //因为计时器里面的作用域只能访问闭包里面函数的作用域。 //如果将i赋给j,相当于拓展了作用域的范围。 setTimeout(function timer(){ console.log(j); },i*1000); })(); }
这样就可以正常输出了,但是还有更好的方案。
for(var i=1;i<=5;i++){ (function(i){ setTimeout(function timer(){ console.log(i); },i*1000); })(i); }
其实就是把所有的作用域都串联在一起,共享了作用域的i的动态值
(3) 重返块作用域:let
for(var i=1;i<=5;i++){ let j=i setTimeout(function(){ console.log(j); },j*1000); }
相当于把循环分割成5个独立的块,单独执行5个不同的函数
什么是let?
let 允许把变量的作用域限制在块级域中。
{ let j=1; var i=1; { var i=2; let j=2; } console.log(i); //2 console.log(j); //1 }
原理:将一个块转换成一个可以被关闭的作用域
个人理解:实际上就是单独声明每个变量的时候,把变量绑定为一个人单独的作用域,跟绑定在单独的函数中原理类似。
(4)模块化:
function coolModule(){ var something="cool"; var another=[1,2,3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another); } return { doSomething:doSomething, doAnother:doAnother } } var foo=coolModule(); foo.doSomething(); foo.doAnother();API的由来:
返回一个对象字面量语法{key:value}来表示对象。
这个用来返回的对象中含有对内部函数而不是内部数据变量的引用。
保持你妹不数据变量是隐藏且私有的状态。
可以将这个对象类型返回值本质上是模块公共的API。
使用模块模式的两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次
2.封闭函数必须返回至少一个内部函数u,并且可以访问或者修改私有的状态
——————————– 华丽的分割线 ———————-
API模块化
var foo=(function coolModule(id){ function change(){ API.ides=id2; } function ide1(){ console.log(id); } function id2(){ console.log(id.toUpperCase()); } var API={ change:change, ides:ide1 }; return API; })("for module"); foo.ides(); foo.change(); foo.ides();
小结
1.作用域:词法作用域和动态作用域
词法作用域:寻找变量以及在何处找到变量的规则
动态作用域:不关心函数和作用域如何声明和在何处声明的,从何处调用。类似于this
区别:词法作用域是在定义书写的时候确定的,而动态作用域是在运行的时候才确定。
ES6前块作用域的替代方案
// ES6 { let a=2; console.log(a); //2 } console.log(a); //ReferenceError ///ES6前-----利用catch分句有块作用域的特点 try{throw 2;}catch(a){ console.log(a); //2 } console.log(a); //ReferenceError
this的简单词法:
- this动态作用域无绑定,导致函数调用错误问题?
var obj={ id:"帅", cool:function coolFn(){ console.log(this.id); } } var id='不帅'; obj.cool(); //帅 setTimeout(obj.cool,100);
等同于
setTimeout(function coolFn(){ console.log(this.id); },100);
访问的居然是全局的id
解决方案:
(1)绑定this,var self=this;
var obj={ count:0, cool:function coolFn(){ var self=this; if(self.count<1){ setTimeout(function timer(){ self.count++; console.log("帅?"); },100); } } } obj.cool();
(2)ES6箭头绑定作用域
var obj={ count:0, cool:function coolFn(){ if(this.count<1){ setTimeout(()=>{ this.count++; console.log("很帅?"); },100); } } obj.cool(); //很帅
(3)用bind绑定this作用域
var obj={ count:0, cool:function coolFn(){ if(this.count<2){ setTimeout(function timer(){ this.count++; console.log("更酷了"); console.log(this.count); }.bind(this),100); } } } obj.count=1; obj.cool();