资讯专栏INFORMATION COLUMN

Effective JavaScript读书笔记(二)

Yuqi / 1004人阅读

摘要:尽可能的使用局部变量,少用全局变量。正确的实现就是在函数体内部使用将声明成局部变量。在新特性中,引入了块级作用域这个概念,因此还可以使用,来声明局部变量。它们共享外部变量,并且闭包还可以更新的值。

变量作用域

作用域,对于JavaScript语言来说无处不在,变量作用域,函数作用域(运行时上下文和定义时上下文),作用域污染等等都跟作用域息息相关,掌握JavaScript作用于规则,可以很好地避免一些极端的情况。

尽量少用全局对象

在JavaScript语言下定义一个全局对象或者变量是最容易不过的了,这种全局变量最容易声明并且使用,因为它能被整个程序所访问到,但是经验丰富的程序员应该尽量避免使用全局变量,它有如下的缺点:

全局变量会污染共享的公共命名空间,可能会导致意外的命名冲突

全局变量不利于模块化,它会导致独立组件之间的不必要耦合

但是,也不是说一定禁止使用全局变量,在一些情况下全局变量的使用是不可避免的,例如,它是各个独立组件之间进行交互的唯一途径。

尽可能的使用局部变量,少用全局变量。

局部变量:IIFE,let,cosnt等。

JavaScript的全局命名空间被暴露为在程序全局作用域中可以访问的全局对象——window,window对象作为this关键字的初始值。在web浏览器中,全局对象全部被绑定在全局命名空间,作为window对象的属性所使用。
this.foo; // undefined
window.foo; // undefined
var foo = "luffy";//声明一个全局变量,会自动绑定到window对象下
this.foo; // "luffy"
window.foo; // "luffy"
全局对象的用处——平台特性检测

可以使用全局对象来判断程序是否在浏览器环境下可以运行

是否支持地理位置

if(navigator.geolocation){
  //user geolocation
}

是否支持HTML5

if (window.applicationCache) {
    alert("你的浏览器支持HTML5");
} else {
    alert("你的浏览器不支持HTML5");
}

等等很多种新特性的检测。(需要时请问BD)

始终声明局部变量
始终使用var来声明一个新的局部变量

在JavaScript中,如果不使用var关键字进行变量声明,该变量会被隐式转换成全局变量,因此会造成不必要的全局空间污染。

function swap(a,b) {
    temp = a; // temp变成了global
    a = b;
    b = temp;
} 

这段程序没有使用var来声明temp变量,最终导致以外的创建了一个从全局的变量temp,虽然代码执行起来也没有错误。正确的实现就是在函数体内部使用var temp;将temp声明成局部变量。

在ES6新特性中,引入了块级作用域这个概念,因此还可以使用let,const来声明局部变量。

避免使用with语句

with语句作用是让代码运行在特定对象作用域内。with语句是JavaScript最令人诟病的特性,不可靠且效率低下。

总而言之一句话,避免使用with语句即可

熟练掌握闭包

努力掌握JavaScript闭包这一概念,对于前端程序员来说会有非常大的帮助。理解闭包不用死背概念,理解三个基本事实就可以。

JavaScript闭包允许你引用在当前函数以外定义的变量
function outerFoo() {
    var outVar = "外部变量";
    return function() {
        console.log("我在函数体内部,访问到了:" + outVar);
    }
}
var foo = outerFoo();
foo(); //我在函数体内部,访问到了:外部变量

上面这个例子,函数内部返回一个函数,对于foo来说,可以访问到自身外部定义的非全局变量(outVar是outerFoo函数体内的局部变量)。

即使函数已经返回,当前函数仍然可以引用在外部函数所定义的变量

仍然是上面那个例子,外部函数已经结束执行过程,返回值赋值给了foo变量(结果是一个函数),现在foo是window作用域下的一个函数,如图所示:

可以看到,window下的foo函数正确的访问到了outFoo函数体内部的局部变量。

原因:JavaScript的函数值包含了比调用它们的时候执行的那部分代码还要多的信息,也就是说它们还在内部存储了它们可能会引用的定义在其封闭作用域内的变量,这种在其所涵盖的作用域内部跟踪变量的函数叫做闭包。

上述的foo函数就是一个闭包,其代码本身引用了外部变量outVar,每一次foo函数被调用,都会引用到这个outVar变量。
对于闭包,有一个很高的评价:闭包是JavaScript最优雅最有表现力的特性之一

闭包可以更新外部变量的值
function box() {
    var val = undefined; //给变量赋值undefined比不给变量赋值要优秀
    return {
        set: function(newVal) { val = newVal; },
        get: function() { return val; },
        type: function() { return typeof val }
    }
}
var b = box();
b.type();//"undefined"
b.set(17.2);
b.get();//17.2
b.type();//"number"

上述例子产生了一个包含三个闭包的对象,这三个闭包分别对应函数返回对象的set、get、type属性。它们共享外部变量val,并且set闭包还可以更新val的值。

实际上,闭包存储的是外部变量的引用,而不是它们真实值的副本.所以闭包是可以更新改变外部变量的值的。

理解变量声明提升

在ES6,JavaScript开始引入块级作用域,使用块级作用域声明的变量只有在包含他们的封闭语句或代码块{}内部可以使用。但是,在ES6之前,是不存在块级作用域的。var声明的变量会被绑定到离它声明最近的那个作用域上,如果找不到就绑定到最外层window上。

function isWinner(player, others) {
    var higest = 0;
    for(var i = 0, n = others.length; i < n; i++){
        var player = others[i]; //此处重复声明了一个player变量
        if(player.score > higest) {
            higest = player.score;
        }
    }
    return player.score > higest;
}

上述函数目的是判断player是否是最高分,但是,在函数内部重复声明了player变量,因此,每一次循环都修改了函数体的传入值player变量本身,最终结果肯定不是预期的。

在ES5版本,JavaScript的变量作用域概念存在两种,函数级作用域和全局作用域。ES6增加了块级作用域,使用let、const可以声明块级作用域变量。

使用立即调用的函数表达式来创建局部作用域

立即执行的函数表达式(IIFE),是前端面试过程基本都会问到的问题。下面使用一个老生常谈的面试题来讲解它:

 window.onload = function() {
    var result = [];
    for (var i = 0; i < 5; i++) {
        result[i] = function() {
            console.log(i);
        }
    }
    console.log(result[1]());// 5
 }

大家应该都知道,最后的执行结果应该是5,并且,result数组内部存的值都是5。

造成结果的原因可以用上面闭包的基本事实来解释,内部函数保存的变量其实是外部变量的引用,也就是说,result的每一个元素内部所引用的i值会随着for循环变化而变化,当我们调用result[1]();这条语句的时候,i值已经变成了5,所以输出是5。
解决办法,就是使用立即执行函数来创建一个局部作用域来解决。

 window.onload = function() {
    var result = [];
    for (var i = 0; i < 5; i++) {
        (function(j) {
            result[j] = function() {
                console.log(j);
            }
        })(i)
    }
    console.log(result[1]());// 1
}

上面使用立即执行函数得到了正确的结果,原因就是它创建了一段块级作用域,立即函数内部将外部变量i的值当做参数穿入内部也就是参数j,之后内部使用的一直都是j这个局部变量,所以就得到了正确的运行结果。

上面IIFE是通过创建一个块级作用域的方式解决的这个问题,其实在ES6中,非常简便的就可以解决,那就是使用let关键字定义i值,因为let定义的变量就是块级作用域变量。for(let i = 0; i < 5l i++)即可得到预期答案。

在使用IIFE的时候要注意几件事:首先,代码块不能包含任何跳出块的break语句和continue语句,因为在函数外部使用break和continue语句是不合法的。其次,如果代码块引用了this或特别的arguments变量,IIFE会改变它们的语义。

当心命名函数表达式笨拙的作用域

这条规则对于现在的环境和正确使用JavaScript语法规则编程的程序员来说不太使用,简单来说就是下面这种情况。

//推荐使用定义函数的方式
//第一种:函数声明
function double(x) {
    return x*2;
}
//第二种:函数表达式
var foo = function(){
    return x*2;
}
//不合理的定义函数的方式
var f = function three(x) {
    return x*3;
}

千万不要使用两种混搭的这种形式,虽然在目前的众多浏览器中都是合法的,但是在低版本终会存在问题,并且,变量three的作用域只是在自身函数体内部,在其他地方都是不能被引用的,而变量f可以被外部引用 。

当心局部块函数声明笨拙的作用域
function foo() {
    return "global";
}

function test(x) {
    function foo() {
        return "local";
    }
    var result = [];
    if (x) {
        result.push(foo());
    }
    result.push(foo());
    return result;
}
test(true); // ["local","local"]
test(false); // ["local"]

这种嵌套函数声明的方式也容易出现问题,全局作用域下声明了函数foo,在test函数体内部又声明了一个foo函数,最后输出的结果是函数体内部foo的执行结果。对于上面这种情况,应该在test函数体内部使用函数表达式的形式声明一个新变量。

function foo() {
    return "global";
}

function test(x) {
    var g = foo;
    var result = [];
    if (x) {
        g = function() {
            return "local";
        }
        result.push(g());
    }
    result.push(g());
    return result;
}
test(true); // ["local","local"]
test(false); // ["global"]
避免使用eval创建局部变量 间接调用eval函数优于直接调用

关于eval函数理解不是很深,没太理解这两节的内容,等有时间从头翻翻红宝书回来再看看。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90067.html

相关文章

  • Effective JavaScript读书笔记(一)

    摘要:如果为假值,不传或者传入,函数都会返回但是,传入这个值是完全有可能的,所以这种判断形势是不正确的或者使用来判断也可以原始类型优于封装类型对象拥有六个原始值基本类型布尔值,数字,字符串,,和对象。 作为一个前端新人,多读书读好书,夯实基础是十分重要的,正如盖楼房一样,底层稳固了,才能越垒越高。从开始学习到现在,基础的读了红宝书《JavaScript高级程序设计》,犀牛书《JavaScri...

    zhoutao 评论0 收藏0
  • JavaScript模式》读书笔记()字面量和构造函数

    摘要:对象字面量定义一个空对象这里的空指的是其自身属性为空,对象继承了的属性和方法添加属性方法完全删除属性方法自定义构造函数用操作符调用构造函数时,函数内部会发发生以下情况创建一个新对象,并且引用了该对象并继承了该函数的原型属性和方法被加入到的引 对象字面量 //定义一个空对象,这里的空指的是其自身属性为空,dog对象继承了Object.prototype的属性和方法 var dog={} ...

    _Zhao 评论0 收藏0
  • JavaScript 语言精粹》 读书笔记 - 函数(

    摘要:对象被传递到从句中被捕获。一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,可以显著提高速度。构建一个带尾递归的函数。语言精粹读书笔记函数 第四章 函数 Functions (二) 参数 arguments arguments数组: 函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括哪些没有被分配给函数声明时定义的形式参数...

    lufficc 评论0 收藏0

发表评论

0条评论

Yuqi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<