js中立即执行函数会预编译吗_js中的预编译,作用域链,立即执行函数,闭包,包装类...

引言

刚刚学完js之后我感觉JavaScript并没有什么东西,好像也不是很难。但是自己运用的时候发现自己学的并不扎实,里面的知识点还是挺多的,甚至有些理解了我还是会出错。所以为了打好基础,我会复习回顾JavaScript的知识点,做好记录,记录是我自己的学习反馈,也是同大家的学习交流,找到自己的不足。这个小记录总结了js中基础且重要的五个问题,看完后能够深入的理解预编译中变量被提升,闭包的作用以及闭包触发哪些问题等知识点。

我会从深度和广度两个方面来叙说。

文章的开始

js语言有两个特点,一个是单线程语言,一个是解释性语言(读一句执行一句)。

知识点1:预编译

1.js执行三步曲

语法分析:通篇分析,通篇扫描一遍看有没有低级错误不进行编译

预编译: 函数整体提升,变量的声明提升

解释执行:解释一行执行一行

ps:定义变量包括变量声明和变量赋值,

任何未经声明就赋值的变量归window所有,window就是全局的域。

一切声明的全局变量非局部变量,就都是window的属性

2.函数中的预编译

预编译发生在函数执行的前一步

1.创建AO对象,AO执行期上下文

2.找出形参和变量声明放到AO{}里面,且值为undefined

3.实参和形参相统一

4.找出函数里面的函数声明并且函数体为值

然后在AO里面拿东西,写的东西是给电脑看的,想拿东西要到电脑里面定义好的地方去拿。而不是视觉上的效果

来看一个栗子

function fn(a) {

console.log(a); //function a() {}

var a = 123;

console.log(a); //123

function a() {}

console.log(a); //123

var b = function() {}

console.log(b); //function() {}

function d() {}

console.log(d); //function d() {}

}

fn(1);

复制代码

这道题包括形参名,函数名,变量名都一样的情况,所以我们不能根据自己看到的去判断,可是看在编译之前生成的执行期上下文。

2.1第一步:创建AO对象

AO{

}

2.2第二步:找出变量声明和形参,值为undefined

AO{

形参a:undefined

变量声明b:unfined

复制代码

}

其中有一个形参a,后边的 var a = 123中有又有一个变量a的声明因为前边有一个形参a所以不用重复写。

2.3第三步:形参实参相统一

AO {

a:1;

变量声明b:unfined

复制代码

}

2.4第四步:找出函数里面的函数声明并且函数体为其值

a,d为函数声明,b为函数表达式

AO {

a:function a() {}

变量声明b:unfined

d:function d() {}

复制代码

}

这a的值由1变成function a(){},是因为a是一个函数声明,所以a的值由1变成了函数体。此时预编译的过程完成。然后再进行编译,解释一行执行一行。预编译过程中执行的代码在编译的时候就不会再执行,如var a = 123; 在执行的过程中就不会再执行var a,变量声明被提升优先执行了。所以直接执行a = 123.

3.全局中的预编译

全局里的预编译与函数里面的预编译类似,第一步创建GO对象,第二步找出变量声明,没有第三步实参和形参相统一。下面结合具体的例子来看看

3.1暗示全局变量

function test (){

var a = b = 124;

console.log(window.a); //undefined

console.log(window.b); //124

}

复制代码

上面的变量b是没有经过声明就赋值了124,所以归window所

4.预编译的例子

4.1恶心的预编译0

var x =1; var y = z = 0;

function add(n) {

return n = n+1;

}

y = add(x);

function add(n) {

return n =n + 3;

}

z = add(x)

复制代码

请输出x,y,z的值。最后结果应该x为1,y和z都是4。因为在预编译的时候同名的函数会被覆盖,而不是以我们的直观感觉来判断。因此执行所以1,2,4的结果是错误的。

4.1恶心的预编译1

//GO{

// test:function test(){};

// a:undefined;

// c:234;

// }

function test (){

console.log(b); //undefined

if(a) {

var b = 100;

}

console.log(b); //undefined

c = 234;

console.log(c);// 234

}

var a;

// AO{

// b:undefined

// }

test();

a= 10;

console.log(c);//234

复制代码

在没有执行test()之前

首先生成GO。window === GO

GO{

test:function test(){};

a:undefined;

}

执行test生成

AO{

b:undefined

}

在test函数中,有一个没有变量声明就赋值的变量c,所以应该归全局所有于是

GO{

test:function test(){};

a:undefined;

c:234;

}

4.2恶心的预编译2

//GO {

global: undifined

fn: function fn(){}

//}

global = 100;

function fn() {

console.log(global);

global = 200;

conaole.log(global);

var global = 300;

}

// AO {

// global:undefined

// }

fn();

var global;

复制代码

首先生成GO,然后再生成AO.第一个打印的global因为fn函数里面就有声明变量global,不需要到GO里面去找。所以第一个打印的就是undefined,不是100,第二个打印global=200.

4.3恶心的预编译3

function bar() {

return foo;

foo = 10;

function foo() {

}

var foo = 11;

}

console.log(bar());//function foo(){}

复制代码

首先console.(bar())就是打印出bar的结果,也就是我们要知道返回的foo的值。根据预编译四部曲我们知道函数声明的优先级最高,所以foo:function foo(){}

console.log(bar());

function bar() {

foo = 10;

function foo(){

}

var foo = 11;

return foo;

}

复制代码

在最后对foo赋值11,所以打印出来不是函数声明而是11.可能有些掘友会问foo是一个局部变量,为什么可以在全局里面输出呢?那是因为全局里访问的是全局里面的bar函数,foo是函数里面的一个变量。

知识点2:作用域链

作用域链与原型链不同,作用域链[[scope]],这里面scope的第一位存放GO,第二位存放AO。作用域链是执行期上下文的集合。

1.执行期上下文:

函数被执行时会创建一个执行期上下文,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,函数每次执行时,都会把新生成的执行期上下文填充到作用域链的最顶端。函数执行完毕,执行期上下文被销毁。

2.如何查找变量

从作用域链顶端依次向下查找。

function a() {

function b() {

function c() {}

c();

}

b();

}

a();

复制代码

这个例子里面有abc三个函数,函数定义的时候产生GO,函数执行前产生GO.作用域链如下

a defined a.[[scope]] ---> 0:GO

a doing a.[[scope]] ---> 0: aAO //aAO表示a的AO

1: GO

b defined b.[[scope]] ---> 0: aAO

1: GO

b doing b.[[scope]] ---> 0: bAO

1: aAO

2: GO

c defined c.[[scope]] ---> 0: bAO

1: aAO

2: GO

c doing c.[[scope]] ---> 0: cAO

1: bAO

2: aAO

3: GO

复制代码

知识点3立即执行函数

1.立即执行函数执行完就可以被销毁,节省空间针对初始化功能的函数

//(function () {})()

//(function (){}()) //w3c 建议的方法

复制代码

上面的括号就是将函数声明变成了函数表达式才可以被执行,至于为什么不写函数名是因为立即函数执行完后函数名被销毁,这两个特点你可能还是不明白,不要着急,看完了立即函数的2,3两点再回来看第一条就明白了。

1.立即执行函数的深入理解

只有函数表达式才能被立即执行符号执行

function test(a, b, c) {

var a = 123;

}(1, 2, 3)

复制代码

最后控制台会报错,而且是低级的语法解析错误。截图如下。因为只有表达式才能够被执行符号()执行。而它只是函数声明所以不能被执行。

如:123为数字表达式,1233+234也叫表达式,不是说只有等于号才叫表达式。

1.1关于=的正确理解

var test = function test() {}应该拆分成var test 和 = function test() {}两部分来理解,前面的是变量声明,后面的由于带有等号所以是函数表达式(变量赋值)。

还有一种函数表达式如果被执行的时候是不会报错的,如:

var test = function test() {

var a = 123;

console.log(a);

}()

复制代码

与此类似,正负号感叹号等可以将函数声明变成函数表达式,乘号和除号不可以。

注意

3.但是出现了一个有趣的现象,test函数被执行完之后再找不到test函数了,相当于立即执行函数。

4.一个神奇的例子(阿里巴巴考试题)

function test(a, b, c) {

console.log(a + b + c);

}(1, 2, 3);

复制代码

不会报错,也不会被执行。按道理是要报错的,但是语法解析将(1, 2,3);看成了函数表达式。

知识点4闭包

内部的函数被保存到外部形成闭包。

闭包的概念 : 一个作用域可以访问另一个函数内部的变量

闭包的作用: 延长变量的作用范围

1.什么是闭包

比如下面的例子里面,a函数里面嵌套了b函数,a函数定义和执行之前生成GO和AO,b函数在定义的时候拿到了a的执行期上下文,并且返回了b函数,那么它的执行期上下文也一同被带回到函数外部。a函数执行完后它的执行期上下文被销毁。外部拿到了被销毁函数a的执行期上下文。

function a() {

var num = 100;

function b() {

num ++;

console.log(num);

}

return b;

}

var demo = a();

demo(); //101

demo(); //102

复制代码

第一次执行demo输出101;b函数被返回,第一次demo执行===b(),在b函数执行前会生成b的AO放在作用域链的第0位,里面没有num,所以王作用域链的第一位也就是a的AO里面找,恰好找到了num=100,num++使aAO{num:101},所以第一次输出num的值是101。然后销毁b的AO

第二次执行demo函数输出102,第二次执行demo也就是执行b函数,b定义的时候拿到的是a的执行期上下文,拿到的是a的劳动成果。b执行之前再次生成b的AO,没有找到mum变量,于是就去a的AO里面寻找,此时aAO{num:101},num ++后,aAO{num:102},然后b执行完后销毁AO。

2.闭包的作用

记得刚开始学习闭包,学完了前男朋友去接我的时候问我学了什么,我说闭包。然后他问我闭包的作用我半天答不上来哈哈哈哈,很多初学者刚刚学完之后都是模棱两可。现在就将闭包的作用进行一次归纳总结。

1.实现共有变量

我们来看一个累加器的例子

function add(){

var count = 0;

function demo(){

count ++;

console.log(count);

}

return demo;

}

var counter = add();

counter();

counter();

counter();

counter();

复制代码

解析

在这个例子里面,add函数里面的demo函数被返回到外部,也就是add函数执行完被销毁的时候,count变量的值却被带到了外部,然后执行一次counter后count的值就加1,实现了累加器的功能。counter调一次就加一次。

2.可以做缓存

闭包可以当一个隐式存储结构,我们来看一个吃香蕉的例子

function eater() {

var food = "";

var obj{

eat : function() {

console.log("i am eating" + food);

food = "";

}

push : function() {

food = myFood;

}

return obj;

}

}

var eater1 = eater();

eater1.push("香蕉");

eater1.eat()// i am eating 香蕉

复制代码

解析

这里面返回来了一个obj对象,该对象里面有两个函数也一起被返回保存到外部,food这个变量可以push函数使用,也可以eat函数使用。两个函数都可以对它的值进行操作,所以说food相当于一个隐式的存储体。

3.可以实现封装,属性私有化

function Deng(name, wife){

var preparewife = 'xiaozhang';

this.name = name;

this.wife = wife;

this.divorce = function() {

this.wife = prerarewife;

}

this.saywife = function() {

console.log(preparewife );

}

}

var deng = new Deng()

复制代码

当控制台访问deng.preparewif的时候,返回结果undefined,preparewife 本来应该是随着函数执行完的执行期上下文销毁而销毁,但是divorce和saywife函数中都有用到preparewife 这个变量,随着this一起被返回,也就是闭包的作用之一—实现封装,实现属性·私有化。所以将隐式,私有,无关,附加的东西放到闭包里面。

4.模块化开发中防止局部污染变量

js作用域

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

我们都知道js中作用域分为全局作用域和局部作用域,局部作用域也是函数作用域。

其他的什么块儿啊、神马文件啊统统都认为是一个作用域,有时候因为一些重名问题导致的错误让人莫名其妙,难以调试解决。

命名空间解决变量名冲突

后来我们把变量函数都放到命名空间中去,命名空间就是对象,为了防止变量名不冲突,用一个对象将这些变量都封装起来。

var obj = {

depart1:{

chen:{

name:'ddd'

},

zhao: {

name: 'fff'

}

},

depart2:{

ling:{

name:''

},

liao: {

neme:''

}

}

}

var linglong = obj.depart1.chen;

chen.name;

复制代码

上面代码可以将不同的部门的员工给封装起来,比如他们的姓名属性不会冲突。这个解决办法就是命名空间。

闭包解决变量名冲突

回到这里应该如何用闭包防止变量冲突问题。

var name = 'linglong';

var init = (function () {

var name = 'xiaoqin';

function callName () {

console.log(name);

}

return fuction (

callname();

) {}

}())

init();

复制代码

我们来看这个代码,init拿到立即执行函数返回的结果后再执行init函数.就相当于执行callname函数,最后结果是xiaoqin,不是linglong,是因为闭包的原因。

我们来仔细分析,全局中执行callname的时后拿到了立即函数中的name属性作为返回结果,如果立即执行函数中没定义name属性才会获取全局中的name,所以最后的结果是linglong。

总结:我们可以通过在一个函数内定义(这里的立即执行函数)一些复杂的功能,然后定义一个接口(函数)并返回给init,当调用init的时候就会启动这个接口实现功能。并且不污染全局变量。

我们把上面的代码扩展,看看结果是什么

var name = 'linglong';

var init = (function () {

var name = 'xiaoqin';

function callName () {

console.log(name);

}

return fuction (

callname();

) {}

}())

init();

var init2 = (function () {

var name = 'sicheng';

function callName () {

console.log(name);

}

return fuction (

callname();

) {}

}())

init2(); //sicheng

复制代码

复习一下,在开发的过程中,不同的人负责不同的功能,但是如果都使用了相同的变量,变量名会产生冲突,为了防止变量名不冲突,讲了两种方法。

虽然现在webpack、gulp等打包工具来解决,但是js是基础,掌握好js学后面才不会吃力。

5.内存泄露

内存泄露就不多说啦,就是闭包内的东西返回到全局中,全局能够拿到闭包内的变量。

构造函数原理

调用new的时候

1.在函数体最前面隐式加上this = {}

2.执行this.xxx = xxx

3.隐式返回this

function Person(name, height) {

var that = {};

that.name = name;

that.height = height;

return that;

}

var person = Person('eee',189);

var person = Person('xx', 120)

复制代码

包装类

原始值可以操作属性,隐式三段式.这儿的原始值是string、boolean、number类型。

//包装类

var num = 4;

num.len = 3;

//new Number(4).len = 3; delete

//

//new Number(4).len

console.log(num.len); //undefined

复制代码var str ="abcd";

str.length=2;

console.log(str);//abcd

var arr=[1,2,3,4];

arr.length = 2;

console.log(arr);//[1,2]

复制代码

原始值没有属性和方法的,至于为什么能够调用是经过了一个包装类的过程,自动创建Number的一个对象然后再给予一个len的属性,创建完后又删掉了这个属性,所以最后是undefined。但是数组有length属性,数组length属性可以被修改。

文章的结束

这篇比较长,而且不是同一个时间段写的,不够好的地方请各位看官指出,觉得我的分享有用的话,欢迎大家关注点赞鸭,后面我将会对js中磨人的this指向做一个全面的总结~~~

最后祝祖国生日快乐,永远和平,繁荣昌盛鸭~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值