我不得不承认我是一个幸运儿,我遇到的每一个人都是如此的优秀。在一群优秀的人中间逐渐修炼我的性格,完善我的技能 真的是一个不可多得的恩赐
一、 作用域
谈到作用域这一块,很多人其实都不怎么熟悉这一块,我今天趁着闲暇跟大家来扯一扯。
js(es6以前)没有块级作用域(你可以自己闭包或其他方法实现),只有函数级作用域,函数外面的变量函数里面可以找到,函数里面的变量外面找不到。
1、 全局作用域
来看下面一个代码
var a = 99;function test(){ console.log("test:",a); } test();// test: 99
从上面的代码我们可以清楚的看出:最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的。
2、局部作用域
function test(){ var a = 99; }console.log("test:",a); test();// ReferenceError: a is not defined
由上面的代码我们可以清楚的知道:局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的。但是有这么种情况:
var a = "hello"function test(){ var a; console.log("test:",a); a=99} test();// test: undefined
当外层函数域定义的变量和函数内定义的变量同时存在的时候,会根据最近原则找本域下的变量,找不到才去外层域去寻找。
3、 块级作用域
谈到块级作用域,这我们就得分es5和es6了,在es5及以前的环境中,先看一段代码:
var a = true;if(a){ var b = "hello world!!!"; }console.log("b:",b) // b: hello world!!!
从上面可以清楚的发现,es6以前是没有块级作用域的。但是在es6中,增加了let
和const
定义方法,就像下面的代码
var a = true;if(a){ let b = "hello world!!!"; }console.log("b:",b) // ReferenceError: b is not defined
4、变量作用域
再看一段代码:
function test(){ a = "hello"} test();// test: helloconsole.log("test:",a);
由上面的代码我可以看出:如果没有定义变量(var,let,const等),默认情况下会将变量变成全局变量。
5、作用域链
关于作用域链,我也来写一段代码
function test(){ a = "hello" var b ="klivitam" function test1(){ console.log("test1",b) } function test2(){ var b = "1221" console.log("test2",b) } test1() // test1 klivitam test2() // test2 1221} test();// test: helloconsole.log("test:",a);
关于作用域链,我的理解是:函数会按照就近原则,首先在本作用域内寻找 如果没有寻到就去外层作用域去寻找一直找到windows环境下。着一系列操作形成一个链式结构,所以被称为作用域链。
二、 js提升
1、 变量、函数提升
关于变量提升,最近看到好多前端基础题目都会提到这个,废话不多说,直接上代码吧。
console.log("global",global);// undefinedvar global = "klivitam";console.log("global",global); // klivitamfunction fn () { console.log(a); // undefined var a = 'aaa'; console.log(a); // aaa} fn();console.log(f1); // [Function: f1]function f1() {} // 函数提升,整个代码块提升到文件的最开始的位置
由上面的代码可以看出来:js会将变量、函数声明提升到它所在作用域的最开始的位置。
2、 值得注意的是
来看下面一段代码
console.log(f2); // undefinedvar f2 = function() {}console.log("a = "+a);console.log("b = "+b);let a = "ni hao!"; // ReferenceError: a is not definedconst b = "nice to meet u"; // ReferenceError: b is not defined
函数字面量是不会进行变量提升的,提升的依然是
var
。const、let也是不会进行变量提升的。
三、 闭包
关于闭包,我的理解是:闭包就是能够读取其他函数内部变量的函数
。
来看下面一段代码:
function f1(){ n=999; return function(){ console.log(n) } }var result = f1(); result(); // 999
我在写文章的时候特意翻了一下js高级程序设计
,里面对闭包是这么描述的:闭包是定义在一个外部函数内部,并且能够访问(存取)外部函数中自由变量的函数。
其实在我的理解里面就是:闭包就是内存函数和外层函数的一个桥梁。
四、 闭包的使用
闭包的作用主要有两个
1、可以读取函数内部的变量
2、让这些变量的值始终保持在内存中。
首先来看一段代码
var count = 99;function outer(){ add = function inside(){ return ++count; } function console1(){ console.log(count); } return console1 }var o = new outer(); o(); // 99add(); o(); // 100
上面的代码就能解释前面所说的两个定义。我们发现第一外层函数能够通过闭包访问到内存函数,并且再次执行的时候,count会进行累加。这说明count值一直处于内存中 没有被回收。如果想要进行回收操作,请在使用完闭包之后:
0 = null;
那关于闭包的使用呢?因为我最近在狂补充基础知识,也多多少少看了一些面试指南什么的,我看到了几个面试题是这样的:
1、 定义五个a标签,然后添加绑定事件,要求点击哪个标签的时候就打印哪个?
function timeCount() { for (var i = 1; i < 5; i++) { // 用settimeout模拟 ,感觉类似 setTimeout(function(){ console.log(i) },2000) } } timeCount(); //5 5 5 5 5
通常的小白都会这么来写,但是结果却发现不能如我们所愿。因为在js中,js会将所有的异步操作加入到一个异步队列中。会等所有的操作结束之后,在进行处理异步操作。所以打印出来的就全部都是5了。为了解决这个问题, 此时就需要用到闭包了。
修改的代码如下:
function timeCount() { for (var i = 1; i < 5; i++) { (function(i){ setTimeout(function(){ console.log(i) },1000) })(i) } } timeCount(); //1 2 3 4 5
2、 单例模式
这个是我在一篇文章里面看到的,我之后也在项目里面尝试用了这种方式,反正我觉得还不错。因为之前接触Android的时候,经常会用到单例模式。所以在这里格外的关注一哈。单例的意思是希望产生一个类的唯一实例,。代码如下:
var singleton = function( fn ){ var result; return function(){ return result || ( result = fn .apply( this, arguments ) ); } }var createMask = singleton( function(){ return document.body.appendChild( document.createElement('div') ); } )
3、 用闭包模拟私有方法
有时候,我们为了隐藏数据和封装功能经常会使用闭包。比如,写一个时间的累加器:
var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } };var counter1 = makeCounter();var counter2 = makeCounter();console.log(counter1.value()); /* Alerts 0 */counter1.increment(); counter1.increment();console.log(counter1.value()); /* Alerts 2 */counter1.decrement();console.log(counter1.value()); /* Alerts 1 */console.log(counter2.value()); /* Alerts 0 */
五、 闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
说在最后
最近一直失眠搞的我整个人都不怎么好,外加上一些私事儿搞得我整个人都有点炸。当然也有自己期待的东西,希望能够成功的,哈哈。总之,我希望我能愉快的渡过这一个阶段,并朝着下一个阶段走去。??? 洗澡去了。
作者:KlivitamJ
链接:https://www.jianshu.com/p/a2d61e813aa0