闭包
一、作用域初探
作用域定义:变量(变量作用于又称上下文)和函数生效(能被访问)的区域
互相嵌套的函数,里面可以访问外面的,外面不能访问里面
外面不访问里面演示:
var a = 123;
function test() {
var b = 123;
}
test(); //
document.write(b);
里面能访问外面演示:
var a = 123;
function test() {
var b = 0;
function demo() {
var c = 234;
console.log(b);//0
console.log(a);//123
}
demo();
// document.write(c);//报错
}
test();
二、js 运行三部曲
JS 逼格:单线程;解释性语言
语法分析,通篇扫描—–>预编译——->解释执行
1.预编译
引入 demo
var a = 123;
console.log(a);//123
console.log(a);
var a = 123;//undefined
函数声明整体提升
function test(){}
变量 声明提升
var a;
document.write(a);
a = 123;
//var a = 123;相当于var a;a = 123
两句话不能解决的问题
console.log(a);
function a(a){
var a = 234;
var a = function(){
}
a();
}
var a = 123;
预编译前奏:
1.imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象 window所有。
function test(){
var a = b =1;
//先b赋值1(未声明),在声明a,在赋值a
}
test();
//var a = 123;先声明a,后赋值
console.log(window.a);
console.log(window.b);
2.一切声明的全局变量,全是 window 的属性。
window 就是全局的域;window 就是全局:
var a = 123;
console.log(a);//---->window.a等价
var a = 123;
window {
a : 123 //相当
}
经典 demo
function test() {
var a = b = 123;
}
test();
console.log(window.b);//undefined,局部,不是全局
console.log(a);
声明局部变量不行 demo
function test(){
var b = 123;
}
test();
console.log(window.b);
console.log(b);
预编译四部曲
预编译发生在函数执行的前一刻
1.创建 AO 对象(Activation object)执行期上下文 2.找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined 3.将实参值和形参统一(GO 没有) 4.在函数体里面找函数声明,值赋予函数体
例子 1
function fn(a){
console.log(a);
// function a(){}
var a = 123;
// var a看过了不看了 直接a=123;
console.log(a);
// 123
function a(){}
// 早已看过不看了
console.log(a);
// 123
var b=function(){}//叫函数表达式
// var b不用看了,看过了,直接b=function(){}
console.log(b);
// function(){}
function d(){}
}
fn(1);
例子 2
function test(a,b) {
console.log(a);
c = 0;
var c;
a = 3;
b = 2;
console.log(b);
function b() {}
function d() {}
console.log(b);
}
test(1);// 答案122
例子 3
function test(a, b) {
console.log(a);
console.log(b);
var b = 234;
console.log(b);
a = 123;
console.log(a);
function a(){}
var a;
b = 234;
var b = function () {}
console.log(a);
console.log(b);
}
test();
预编译不止发生在函数体系里面,还发生在全局,全局叫生成了一个 GO 对象,名字不同,步骤一样。 window 就是 GO
// GO{
// b : 123;
// }
function test(){
var a = b = 123;
console.log(window.b);//能访问
}
test();
//AO {
// a:undefiend
// 对b不起作用
//}
// 但是GO里没有a,so window.a没有
先生成 GO(全局),后 AO
// GO{
// test:function(){...}
// }
console.log(test);
function test(test) {
console.log(test);
var test = 234;
console.log(test);
function test() {
}
}
test(1);
var test = 123;
// AO{
// //test执行的前一刻
// // GO,AO里面都有test,要AO的
// }
难度 1:
// GO{
// global : undefined---->100
// fn : function (){...}
// }
var global = 100;
function fn(){
console.log(global);
}
// AO{
// 没东西了,就打印把,global自己里面没有,再去GO里面找到了
// }
fn();
难度 2:
global = 100;
function fn(){
console.log(global);
global = 200;
console.log(global);
var global = 300;
}
fn();
var global;
// AO{
// global:undefined
// }
//答案:undefined 200 有自己的先用自己的
难度 3
// GO{
// //GO里面才有a
// a : undefined
// c : 234暗示全局变量
// }
function test() {
console.log(b);//undefined
if(a) {
var b = 100;
}
console.log(b);//undefined,因为if不走
c = 234;//暗示全局变量
console.log(c);//234
}
var a;
test();
// AO{
// b : undefined
// }
a = 10;
console.log(c);//234
百度题
function bar(){
return foo;
foo = 10;
function foo(){
}
var foo = 11;
}
console.log(bar());
//答案:function foo(){}return 下面有函数,返回函数
百度题
console.log(bar());
function bar(){
foo = 10;
function foo(){}
var foo = 11;
return foo;
//return 前面覆过值,就11
}
变量 声明提升
console.log(b);//undefined
var b = function () {
}
顶级难度
// GO{
// a : undefined,
// demo : function () {}
// 然后a:100;在demo执行的前一刻,产生AO
// f :123
// }
a = 100;
function demo(e){
function e() {}
arguments[0] = 2;
console.log(e);//2
if(a) {//如今if里面不能放函数声明了//a是undefined,里面语句不走了
var b = 123;
function c() {
//猪都能做出来
}
}
var c;
a = 10;
var a;
console.log(b);//undefined
f = 123;//f AO里面没有,扔给GO 暗示全局变量
console.log(c);//func unde
console.log(a);//10
}
var a;
AO{
//形参
e : undefined--->1---->function e(){}--->2
b : undefined
c : undefined--->(function (){})
a : undefined--->10
}
demo(1);
// 以下为函数外面了,全局了,GO
console.log(a);//100
console.log(f);//123
姬成题目
//typeof(null);----object
var str = false + 1;
document.write(str);//1
var demo = false == 1;
document.write(demo);//false
if(typeof(a)&&-true + (+undefined) + ""){
document.write('基础扎实');
}//"undefined"&&-1 + "NAN"字符串类型 +""
if(11 + "11" * 2 == 33) {
document.write('基础扎实');
}
!!" " + !!"" - !!false||document.write('你觉得能打印,你就是猪');
// " "空格字符串true
// ""空串 false
// true + false - false == 1就停了
// 1||(被省略了)
笔试题:
1.css 中,display 属性几种,列出来
display : none/block/inline-block/inline……
2.css 中 list-style 的属性有几种,分别是什么
3.
<div class="box">
<div class="box_1"></div>
<div class="box_r"></div>
</div>
平行排列,并且均分父级,并且没有间距//两栏布局 4.使用 CSS HTML 三角形 5.水平垂直居中 6.写出 window.foo 值
(window.foo || window.foo = 'bar');//这样报错,这样先计算或,优先级高
(window.foo || (window.foo = 'bar'));//先看括号,bar
三、作用域精解
1.单线程 2.解释性语言(翻译一句解释一句)
[[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。 3.运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象 AO。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
查找变量:从作用域链的顶端依次向下查找。
var a = 100;
function test() {
var a = 234;
console.log(a);//自己里面有,就不要GO,无论如何说明AO与GO有某种联系
}
test();
小例子
function a(){
function b() {
var b = 234;
}
var a = 123;
b();
console.log(a);
}
var glob = 100;
a();
// a defined a.[[scope]]---> 0:GO{}
// a.doing a.[[scope]]---> 0:AO{}
// 1:GO{}
两个 AO 是引用,指向同一个房间
function a(){
function b() {
var bb = 234;
aa = 0;
}
var a = 123;
b();
console.log(aa);
}
var glob = 100;
a();
例子
function a() {
function b() {
function c(){
}
c();
}
b();
}
a();
a defined a.[[scope]] --->
0:GO
a doing a.[[scope]] --->
0 : aAO
1 : GO
b defined b.[[scope]] --->
0:aAO
1:GO
b doing b.[[scope]] --->
0:bAO
1:aAO
2:GO
c defind c.[[scope]] --->
0:bAO
1:aAO
2:GO
c doing c.[[scope]] --->
0:cAO
1:bAO
2:aAO
3:GO
四、闭包
1.定义:
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露
2.闭包例子
例子 1
function a() {
function b(){
var b = 234;
console.log(aaa);//123
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
例子 2
function a() {
var num = 100;
function b() {
num ++;
console.log(num);
}
return b;
}
var demo = a();
demo();//101
demo();//102
3.闭包的作用
实现公有变量
eg:函数累加器,之前依赖外部变量
function add() {
var count = 0;
function demo(){
count++;
console.log(count);
}
return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
counter();
可以做缓存
eg:eater
function test() {
var num = 100;
function a(){
num++;
console.log(num);
}
// a defined a.[[scope]] 0 : testAO
// 1 : GO
function b(){
num--;
console.log(num);
}
// b defined b.[[scope]] 0 : testAO
// 1 : GO
return [a,b];
}
var MyArr = test();
MyArr[0]();
// a doing a.[[scope]] 0 : aAO
// 1 : testAO *
// 2. GO
MyArr[1]();
// b doing b.[[scope]] 0 : bAO
// 1 : testAO *
// 2. GO
// 因为a,b是一个爹,环境一样,a=b
缓存演示 demo
function eater() {
var food = "";
var obj = {
eat : function (){
console.log("i am eating " + food);
food = "";
},
push : function(myFood) {
food = myFood;
}
}
return obj;
}
var eater1 = eater();
eater1.push('banana');
eater1.eat();
可以实现封装,属性私有化
eg: Person();
模块化开发,防止污染全局变量
函数声明&&函数表达式区别
4.闭包的防范
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生
五、立即执行函数
定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作
针对初始化功能的函数。立即执行函数用完销毁和函数没区别
var num = (function (a, b, c){
var d = a + b + c;
return d;
}(1, 2, 3))
立即执行函数的两种写法:
- (function (){}()); W3C 建议第一种
- (function (){})();
现象
function test() {//函数声明
var a = 123;
}();//语法解析错误
function test() {
var a = 123;
}
test();//test是表达式,就可以
以上,只有表达式才能被执行符号执行
function test() {
var a = 123;
}//函数声明
123;//函数表达式
test();//函数表达式
// 函数表达式可以
var test = function() {
console.log('a');
}();
// 能被执行符号执行的表达式,这个函数名字就会被自动忽略
var test = function() {
console.log('a');
}
var test = function() {
console.log('a');
}();//立即执行
+ function test(){
console.log('a');
}();//减号,叹号,乘除不行(加减代表正负),&&,||
// 惊悚的
(function test() {
console.log('a');
})()
(function test {
console.log('a');
}())
// 因为被执行符号执行的表达式,这个函数名字就会被自动忽略
(function () {
console.log('a');
}())
//立即执行函数很多形式
阿里巴巴
function test(a,b,c,d) {
console.log(a+b+c+d);
}(1,2,3,4);
//相当于
function test(a,b,c,d) {
console.log(a+b+c+d);
}
(1,2,3,4);
重点引入样例:
function test () {
var arr = [];//里面存了十个函数
for( var i = 0; i < 10; i++) {
arr[i] = function () {
document.write(i + ' ');
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
myArr[j]();
}
为什么是 10
i++
为什么全是 10
arr[i] = function () {
document.write(i + ' ');
}
是一个赋值语句,把一个函数引用赋给数组当前位,函数体 document.write(i + ‘ ‘);没调用前不执行,直到调用,才执行,恰好执行的时候全是十。
执行位置!=定义位置
怎么解决(唯一方法) 十个小立即执行函数吧
function test () {
var arr = [];//里面存了十个函数
for( var i = 0; i < 10; i++) {
(function (j){
arr[j] = function () {
document.write(j + " ");
}
}(i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
myArr[j]();
}//打印i依旧是10,
//立即执行函数是读到马上执行
六、闭包
var demo;
function test() {
var abc = 100;
function a(){
console.log(abc);
}
demo = a;
}
test();
demo();
//里面的保存到外部,生成闭包
阿里巴巴笔试题(UC 移动事业群)社招题
function test() {
var liCollection = document.getElementsByTagName('li');
for(var i = 0; i < liCollection.length; i++){
liCollection[i].onclick = function(){
console.log(i);
}
}
}
test();
<!DOCTYPE html>
<html>
<head>
<title>hehe</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
li:nth-of-type(2n) {
background-color: red;
}
li:nth-of-type(2n + 1) {
background-color: green;
}
</style>
</head>
<body>
<ul>
<li>a</li>
<li>a</li>
<li>a</li>
<li>a</li>
</ul>
<script type="text/javascript">
function test() {
var liCollection = document.getElementsByTagName("li");
for (var i = 0; i < liCollection.length; i++) {
(function (j) {
liCollection[i].onclick = function () {
console.log(j);
};
})(i);
}
}
test();
</script>
</body>
</html>
笔试题
var x = 1;
if(function f() {}){
x += typeof f;
}
console.log(x);//1undefined