《你不知道的JavaScript》-- 精读(三)

知识点

1.函数中的做用域

函数做用域的含义是指,属于这个函数的所有变量均可以在整个函数的范围内使用及复用(事实上在嵌套的做用域中也可使用)。bash

function foo(a){
    var b = 2;
    // 一些代码
    function bar(){
        // ...
    }
    // 更多的代码
    var c = 3;
}
bar(); // 失败
console.log(a,b,c); // 三个全都失败
复制代码

2.隐藏内部实现

能够把变量和函数包裹在一个函数的做用域中,而后用这个做用域来“隐藏”它们。函数

为何“隐藏”变量和函数是一个有用的技术?ui

最小特权原则:在软件设计中应该最小限度地暴露必要内容,而将其它内容都“隐藏”起来,好比某个模块或对象的API设计。spa

function doSomething(a){
    b = a + doSomethingElse(a * 2){
        console.log(b * 3);
    }
}
function doSomethingElse(a){
    return a - 1;
}
var b;
doSomething(2); // 15

// 最小特权原则改进
function doSomething(a){
    function doSomethingElse(a){
        return a - 1;
    }
    var b;

    b = a + doSomethingElse(a * 2){
        console.log(b * 3);
    }
}

doSomething(2); // 15
复制代码

3.规避冲突

“隐藏”做用域中的变量和函数所带来的另外一个好处,是能够避免同名标识符之间的冲突。设计

function foo(){
    function bar(a){
        // var i = 3;
        i = 3; // 修改for循环所属做用域中的i
        console.log(a+i);
    }
    for(var i = 0; i < 10; i++){
        bar(i * 2); // 糟糕,无限循环了!
    }
}
复制代码

可使用var i = 3修改上面的代码,获得正确的结果。在这种状况下使用做用域来“隐藏”内部声明是最佳选择。code

3.1 全局命名空间

变量冲突的一个典型例子存在于全局做用域中。当程序中加载了多个第三方库时,若是它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引起冲突。对象

这些库一般会在全局做用域中声明一个名字足够独特的变量,一般是一个对象,被用做命名空间,全部须要暴露给外界的功能都是该对象的属性。ip

var MyReallyCoolLibrary = {
    awesome: "stuff",
    doSomething: function(){
        // ...
    }
    doAnotherThing: function(){
        // ...
    }
}
复制代码

3.2 模块管理

另一种避免冲突的方法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来使用。作用域

4.函数做用域

在任意代码片断外部添加包装函数,能够将内部的变量和函数定义“隐藏”起来,外部做用域没法访问包装函数内部的任何内容。string

var a = 2;
function foo(){ // 添加这一行
    var a = 3;
    console.log(a);
} // 以及这一行
foo(); //  以及这一行
console.log(a);
复制代码

这样会致使的问题是,首先必须声明一个具名函数foo(),意味着foo这个名称会“污染”所在做用域(在这个例子中是全局做用域)。其次,必须显示地经过函数名(foo())来调用才能运行。

JavaScript提供了可以同时解决函数不须要函数名,而且可以自动运行的方案。

var a = 2;
(function foo(){
var a = 3;
console.log(a); // 3
})()
console.log(a); // 2
复制代码

函数声明和函数表达式的区别是function是不是声明的的第一个词,若是是就是函数声明。

(function foo(){..})做为函数表达式意味着foo只能在..所表明的位置中被访问。外部做用域则不行。foo变量被隐藏在自身中意味着不会非必要地污染外部做用域。

5.当即执行函数表达式(IIFE)

始终给函数表达式命名是一个最佳实践。

setTimeout(function timeoutHandler(){ // 快看,我有名字了!
    console.log("I waited 1 second");
},1000)
复制代码

(function(){...})(),第一个()将函数变成表达式,第二个()执行了这个函数。

IIFE的进阶用法是把它们当作函数调用并传递参数进去。

var a = 2;
(function IIFE(global){
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})(window)
console.log(a); // 2
复制代码

IIFE的另外一种用途是倒置代码的运行顺序。

var a = 2;
(function IIFE(){
    def(window);
})(function def(global){
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})
复制代码

函数表达式def定义在片断的第二部分,而后当作参数(这个参数也叫作def)被传递进IIFE函数定义的第一部分中。最后,参数def(也就是传递进去的函数)被调用,并将window传入当作global参数的值。

6.块做用域

6.1 let

let 关键字能够将变量绑定到所在的任意做用域中(一般是{..}内部)。let为其声明的变量隐式地劫持了所在的块做用域。

var foo = true;
if(foo){
    let bar = foo * 2;
    bar = something(bar);
    console.log(bar);
}
console.log(bar); // ReferenceError
复制代码
var foo = true;
if(foo){
    { // 显式的块
        let bar = foo * 2;
        bar = something(bar);
        console.log(bar);
    }
}
console.log(bar); // ReferenceError
复制代码

let 进行的声明不会在块做用域中进行提高。

{
    console.log(bar); // ReferenceError
    let bar = 2; 
}
复制代码

6.2 const

var foo = true;
if(foo){
    var a = 2;
    const b = 3; // 包含在if中的块做用域常量
    a = 3; // 正常
    b = 4; // 错误!
}
console.log(a); // 2
console.log(b); // ReferenceError
复制代码

总结

函数是JavaScript中最多见的做用域单元。本质上,声明在一个函数内部的变量或函数会在所处的做用域中“隐藏”起来,这是有意为之的良好的软件设计原则。

但函数不是惟一的做用域单元。块做用域指的是变量和函数不只能够属于所处的做用域,也能够属于某个代码块(一般指{...}内部)。

巴拉巴拉

关于标签

一朋友在我对本身产生严重质疑的时候对我说,不要轻易给本身加标签,固然也不要轻易给别人下定义。聊这个事情的时候,并无醍醐灌顶,却是最近经历的一些事,让我对他说的话,有了一些不同的感悟。好比,你某件事没有作好,你的上级可能会以为你没作好的缘由是不够认真,经验不足,能力不够等,一旦他把其中的某个词加在你身上,你的第一反应可能他是对的,我就是这样的,甚至还会自我加注更多相似的。我想说这样是不对的,由于你可能无心中就多了一个困扰本身的负面标签。正确的作法应该是,他也许是对的,我还有须要提高,改正的地方,尽可能避免正面的去赞成他的话,而是从积极的角度说能够改正,提高的地方,不少事,是没有绝对的对错和好坏的,一个原则是,尽可能不要给本身加注负面的具体的标签,由于会给人更深的印象,让人永远记住黑历史。

相关文章
相关标签/搜索