-
执行上下文
1.1 对变量来说,js
在执行代码之前只先声明变量,不执行赋值语句。
变量赋值是在执行到赋值语句的时候才进行。
所以在已经声明的变量之前访问这个变量,访问到的是js
自动给的初始值undefined
console.log(a); // undefined var a = 10; // 进行赋值 console.log(a); // 10
1.2 对
this
来说,直接给this
赋值
1.3 在处理函数声明时和1.1情况一样处理
在处理函数表达式时,是对函数名直接赋值的,能够提前访问到函数console.log(f1); // function f1() {} function f1() {} console.log(f2); // undefined var f2 = function () {}
总结:
生成执行上下文做的数据准备工作:
1. 变量声明默认赋值为undefined
2. this直接赋值
3. 函数声明直接赋值1.4 执行上下文可以有这三个代码段环境:全局代码、函数体、
eval
代码
对函数来说,在函数中除了总结中说了数据之外,还有其他数据:函数的参数、arguments
变量、自由变量的取值作用域,这三个也是直接赋值的
1.4.1 函数每被调用一次,都会产生一个新的执行上下文环境
1.4.2 函数在定义的时候就已经确定了函数体内自由变量的作用域var a = 'kimi'; function fn(x){ console.log("arguments", arguments); // arguments对象 console.log("x", x); // 参数 console.log("a", a); // 自由变量 } function bar(f){ var a = 10; f(); // 要到创建这个函数的那个作用域中取值 } fn('emoji'); bar(fn); 打印输出: // arguments Arguments ["emoji", callee: ƒ, Symbol(Symbol.iterator): ƒ] // x emoji // a kimi // arguments Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ] // x undefined // a kimi
1.4.3 自由变量
在fn
作用域中使用的变量a
没有在fn
作用域中声明,对fn
作用域来说,a
就是一个自由变量
1.4.4 作用域链
如果要在函数中找当前作用域中没有声明的自由变量,要到创建这个函数的作用域中去找,没有找到就继续向上直到找到全局作用域为止。不管这个函数是在哪里调用的var a = 10; function fn(){ var b = 20; function bar(){ console.log(a+b); } return bar; } var x = fn(), b = 200; x(); // 30
-
fn()
的this
指向是x
,x
调用fn
函数 - 先在当前作用域
bar
中去找,没有就在fn
函数中查找b
,因为bar
中没有定义变量,所以a
b
对bar
来说都是自由变量,要找a
b
要到创建bar
函数的作用域中去找,那就是fn
,fn
中有b
就b=20
,fn
中没有找到a
,就接着到创建fn
的作用域,全局作用域中去找,找到a=10
-
-
this
this
的取值是执行上下文中的一部分,每次调用函数都会产生一个新的执行上下文。只有在函数被执行调用的时候才能确定this
指向。
2.1 构造函数的this
指向(new
)构造出来的实例对象
在构造函数的prototype
中,this
也指向实例对象function Fn() { this.name = 'emoji'; this.year = 1988; } Fn.prototype.getName = function(){ console.log(this.name); } var f1 = new Fn(); f1.getName(); // 'emoji'
2.2 函数作为对象的一个属性并且被对象调用时,函数中的
this
指向该对象
2.3 函数用call
、apply
调用时,this
的值是传入的对象的值
2.4 全局调用时,this
是window
2.5 作为普通函数调用时,this
是window
2.6 作为html
元素事件中的this
时,this
是当前事件发生的目标元素 -
作用域
只有全局作用域和函数作用域,每个函数创建都会创建一个自己的作用域
一个作用域中会存在多个执行上下文环境
js
没有块({}
中的语句)级作用域,所以不要在块中声明变量,一般都提前进行声明var i = 10; if(i>1){ var name = 'emoji'; } console.log(name); // emoji
作用域类似于地盘的概念,作用域就像隐形的围墙,将自己地盘上的代码围住
除了全局作用域外,每个函数都会创建自己的作用域,这是在函数定义的时候就确定好的
作用域中的变量的值是在执行过程中确定的,作用域不涉及变量,变量是作用域对应的执行上下文环境来影响的
作用域有上下级的关系,上下级关系的确定看函数在哪个作用域下创建,
bar
的上级就是fn
作用域最大的用处就是隔离变量,不同作用域下同名变量不会冲突
TODO 作用域链的查找过程
var a = 10, b = 20; // 全局作用域 function fn() { // fn 的作用域 var a = 100, b = 200; function bar() { // bar 的作用域 var a = 1000, b = 2000; } bar(100); bar(200); } fn(10);
执行上下文环境和作用域说明:
- 在加载代码的时候,全局上下文环境 变量、函数声明 然后赋值
- 执行到
fn(10)
,生成调用fn函数的上下文环境,压栈,当前活动上下文环境是fn - 执行到
bar(100)
,生成调用bar函数的上下文环境,压栈,当前活动上下文环境是bar
,执行完bar(100)
,当前上下文环境销毁;执行bar(200)
,同上面一样,bar(200)
执行完销毁后回到上级fn
的上下文环境中,fn
为活动状态 - 执行完
fn(10)
后,销毁fn(10)
的上下文环境,回到全局上下文环境中,当前活动状态为全局
-
闭包
TODO 自己写一个闭包
什么是闭包?
函数a
内部有一个函数b
,函数b
可以访问到函数a
中的变量,函数b
就是闭包
闭包存在的意义:可以间接访问函数内部的变量function A() { let a = 1 window.B = function () { console.log(a) } } A() B() // 1
匿名函数是在
A
函数内被声明的,所以从创建这个函数的内部找变量循环中使用闭包解决
var
定义函数的问题涉及事件循环机制,
settimeout
机制function wait(message){ setTimeout(function timer(){ console.log(message); }, 1000); } wait("Hello Message"); // 'Hello Message'
延迟函数的回调会在循环结束时才执行
for(var i = 1; i <= 5; i++){ setTimeout(function timer() { // 执行这个函数的时候for循环已经执行完 console.log(i); }, i*1000); } // 6
改造成闭包,在循环内使用立即调用函数表达式(IIFE),以便在每次迭代中强制创建变量的一个新副本:
for(var i = 1; i <= 5; i++){ (function (j) { setTimeout(function timer() { console.log(j); }, j*1000) })(i); }
第二种就是使用
setTimeout
的第三个参数,这个参数会被当成timer
函数的参数传入。for (var i = 1; i <= 5; i++) { setTimeout( function timer(j) { console.log(j) }, i * 1000, i ) }
第三种就是使用
let
定义i
来解决问题了,这个也是最为推荐的方式-
let
和var
的区别(待补充)
用let
声明的变量,不存在变量提升。而且要求必须 等let
声明语句执行完之后,变量才能使用,不然会报Uncaught ReferenceError
错误。
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }
4.1 函数作为返回值
function fn(){ var max = 10; return function bar(x){ if( x > max ){ console.log(x); } } } var f1 = fn(); f1(15); // 15
- bar函数作为返回值赋值给变量f1
- 涉及跨作用域取值
4.2 函数作为参数传递
var max =10, fn = function (x){ if(x > max){console.log(x);} }; (function (f){ var max = 100; f(15); })(fn); // 15 > 10 -> 15
- 要到创建这个函数的作用域上下文环境中去找变量
4.3 在执行上下文栈中,提到当一个函数被调用完成之后,这个函数的执行上下文环境会被销毁,包括其中的变量。但是有些情况下函数调用完成后,其执行上下文环境不会被直接销毁
function fn(){ var max = 10; return function bar(x){ if(x > max){ console.log(x); } } } var f1 = fn(), max = 100; f1(15); // 15
- 代码执行前生成全局上下文环境,并在执行的时候对变量赋值,这时候全局上下文环境是活动状态。
max -> undefined
- 执行
f1 = fn()
,调用fn
函数,这时候产生了fn
执行上下文环境,压栈,为活动状态 - 但是在
f1
变量赋值时fn()
调用完的时候,这里不会销毁fn
函数的上下文执行环境,因为fn
函数内部返回的是一个函数bar
,函数bar
会创建一个独立的作用域,bar
是在执行fn
函数时创建的,fn
其实早就执行结束了,但是上下文环境还留着。如果销毁了fn
,bar
中的max
就找不到值了。 - 执行
f1(15)
函数调用bar
,max
在bar
中是自由变量,需要在创建bar
的函数fn
中找,找到Max=10
,就取10
-
JS基础知识-作用域和闭包
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 引言 可以说是取名废了,把这几个关键词放在一起也是因为看完犀牛书相关章节和很多技术文章之后觉得这几个概念是相互渗透...
- 王福朋 - 博客园 —— 《 深入理解javascript原型和闭包》 目录:深入理解javascript原型和闭...
- 1.对象是什么 对象就是若干属性的集合。 在JS中一切引用类型都是对象:数组是对象,函数是对象,对象还是对象。对象...