首页 > 代码库 > 有关作用域的知识
有关作用域的知识
内容摘自:
你不知道的JavaScirpt上册
并作了一些总结
什么是作用域?
三大角色:引擎,编译器,作用域
- 引擎:负责JS的编译和执行——大佬
- 编译器:负责语法分析和代码生成——二哥
- 作用域:对声明变量查询,确定标识符的访问权限—三哥
示例:
var a =2;
///
var a;
a=2;
可以看成是两步的操作:
1. 变量的赋值会执行两个动作,首先编译器会在当前作用域中声明一个变量()
2. 然后在运行时引擎会在作用域中查找该变量,如果找到会对它赋值
LHS(left)和RHS(right)怎么理解?
赋值操作的目标是谁LHS。
谁是赋值操作的源头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();
有关作用域的知识