美文网首页程序员首页投稿(暂停使用,暂停投稿)
《你不知道的JavaScript 上》读书笔记

《你不知道的JavaScript 上》读书笔记

作者: 一颗板栗_ | 来源:发表于2016-08-02 18:10 被阅读340次

    标签: js 还有原型链要更新呀


    作用域

    • 作用域是查找变量的一套规则
    • 如果查找的目的是对变量进行赋值,则进行LHS查询;如果查找的目的是获取变量的值,则进行RHS查询。Left/Right Hand Sidei,变量在等号左边或等号右边
    • LHS查询:赋值操作,如=操作符、调用函数时传参
     function foo(e) {
        var b = a;
        return a + b;
    }
    var c = foo(2);
    
    • 例子中LHS查询有三处:b=a c=foo(2) 把2赋给a(隐式变量分配)
    • RHS查询有四处:foo(2) =a a b
    • 异常
      • ReferenceError,引用一个未被定义的变量。
      • TypeError,对结果的操作不合理,如引用一个undefined类型的值的属性
      • SyntaxError,语法错误
    • 如何判断当前是否在严格模式下:函数直接调用时this指向运行时环境的全局对象。在非严格模式下,浏览器中全局对象为Window,Nodejs控制台中全局对象为Global;在严格模式下,全局对象是undefined。因此可判断如下
    var isStrict = (function(){return !this;}());
    console.log('isStrict ' + isStrict);
    
    • 编译。传统编译语言,代码执行前需要编译,包括三个过程:词法分析(将代码拆分为词法单元)、语法解析(将词法单元数组转换为语法树)、代码生成(生成可执行代码)

    词法作用域VS动态作用域

    • 编译的词法分析阶段知晓各标识符及如何声明的,因此可以在执行过程中进行查找。词法作用域意味着作用域在书写代码时函数声明的位置决定。
    • eval with
    • 词法作用域又叫静态作用域,和动态作用域的区别是动态作用域是在运行时而非定义时确定的。词法作用域关注函数在何处声明,动态作用域关注函数从何处调用,其作用域链是基于运行时的调用栈
    • wiki:大多数现在程序设计语言都是采用静态作用域规则,如C/C++、C#、Python、Java、JavaScript;采用动态作用域的语言有Pascal、Emacs Lisp、Common Lisp(兼有静态作用域)、Perl(兼有静态作用域)。C/C++是静态作用域语言,但在宏中用到的名字,也是动态作用域。
    // 词法作用域:
    function foo() {
        print a;    // 输出2
    }
    
    function bar() {
        var a = 3;
        foo();
    }
    
    var a = 2;
    bar();
    
    // 动态作用域
    function foo() {
        print a;    // 输出3而不是2
    }
    
    function bar() {
        var a = 3;
        foo();
    }
    
    var a = 2;
    bar();
    

    函数作用域

    • 最小特权原则
    • 在软件设计中,应该最小限度地暴露必要内容,而将其隐藏起来,比如某个模块或对象的API设计。
    • 避免暴露私有的变量或函数,避免其被有意或无意地以非预期的方式使用。
    function doSomething(a){
        b = a + doSomethingElse(a * 2);
        console.log(b * 3);
    }
    function doSomethingElse(e){
        return e - 1;
    }
    var b;
    doSomething(2); // 15
    
    • 上例中b doSomethisElse均应是doSomething的内部内容,改进:
    function doSomething(a){
        function doSomethingElse(e){
            return e - 1;
        }
        var b;
        b = a + doSomethingElse(a * 2);
        console.log(b * 3);
    }
    doSomething(2); // 15           
    
    • 立即执行函数表达式IIFE 的两种形式
    (function(){console.log('a');})() // a
    // 第一个()将函数变成表达式,第二个()立即执行这个函数
    
    +function(){console.log('a');}() // a
    // + 也会将函数变成一个表达式,js会解析成运算(bootstrap的js源码采用此种方式),第二个()立即执行这个函数
    
    js中神奇的 +
    new Date(); // Fri Aug 05 2016 16:58:08 GMT+0800 (CST)
    + new Date; // 1470387494773 (一个时间戳)
    
    // 时间戳:unix时间,从`协调时间时`1970年1月1日0时0分0秒起至现在的总秒数
    
    • IIFE进阶1:当作函数调用并传参,提高可读性,改进代码风格。
    var a = 2;
    (function IIFE(global){
        var a = 1;
        console.log(a); // 1
        console.log(global.a); // 2
    })(window);
    
    • IIFE进阶2:倒置代码的运行顺序,将需要运行的代码放在第二位,在IIFE执行后当作参数传递进去
    var a = 2;
    (function IIFE(def){
        def(window);
        console.log(a); // 2
    }(function def(global){    
        var a = 1;
        console.log(a); // 1
        console.log(global.a); // 2
    }));
    

    块级作用域

    let ES6变量声明

    • 隐式绑定在所在块级作用域
    var foo = true;
    if (foo) {
        var bar = foo * 2;
        console.log(bar); // 2
    }
    console.log(bar); // 2
    
    var foo = true;
    if (foo) {
        let bar = foo * 2;
        console.log(bar); // 2
    }
    console.log(bar); // ReferenceError: bar is not defined
    
    • 显式创建一个块级作用域
    var foo = true;
    if (foo) {
        {
            let bar = foo * 2;
            console.log(bar); // 2
        } // 一个显式的块
        console.log(bar); // ReferenceError: bar is not defined
    }
    console.log(bar); // ReferenceError: bar is not defined
    
    • let声明的变量在块级作用域中不会提升
    • 垃圾回收,let块级作用域中的内容执行完后被销毁
    • let循环
    for(let i=0;1<10;i++) {
        console.log(i);
    }
    console.log(i); // ReferenceError: i is not defined
    
    • let将i绑定在for循环的块中,不会污染全局变量,并且将其重新绑定在循环的每一次迭代中。

    const ES6变量声明

    • const声明的变量只在声明时赋值,不可修改,否则会报错
    • const声明的变量必须在声明时就赋值。
    var foo = true;
    if(foo) {
        var a = 2;
        const b = 3;
        const c = {};
        const d; // SyntaxError: Missing initializer in const declaration
        a = 3;
        b = 4; // TypeError: Assignment to constant variable
        c.a = 1; 
        console.log(c); // {a: 1}
        console.log(c.a); // 1
    }
    console.log(a); // 3
    console.log(b); // ReferenceError: b is not defined
    

    提升(hoisting)

    • 理解提升:引擎会在解释js代码前首先进行编译,编译的一部分工作就是找到所有声明,并用合适的作用域将他们包裹起来。所有声明会在代码执行前被处理。
    • 编译阶段声明会被提升,赋值或其他运算逻辑留在原地在执行过程中被处理。
    • 函数声明提升的同时,包含了实际函数的隐藏值。
    foo(); // undefined;
    function foo() {
        console.log(a);
        var a = 1;
    }
    // 此时foo()可正常调用,foo()函数内部也有变量提升
    
    • 函数表达式和变量声明类似,变量标识符会提升,赋值没有被提升
    foo(); // TypeError: foo is not a function
    var foo = function() {
        console.log(a);
        var a = 2;
    }
    // 此时报错不是ReferenceError,因为此时变量标识符foo被提升到了顶部,var foo;但赋值未被提升,此时foo的值是undefined,对一个undefined值进行函数调用是非法操作,因此抛出TypeError异常
    
    • 提升中函数优先
    foo(); // 1
    var foo;
    function foo() { 
        console.log(1); 
    } 
    foo = function() {
        console.log(2);
    }
    

    引擎解释:

    function foo() { 
        console.log(1); 
    } 
    foo(); 
    foo = function() {
        console.log(2);
    }
    // var foo;是重复声明被忽略。
    foo(); // 此时再调用foo()值为2,第二次定义的函数表达式覆盖了前面的函数声明。
    
    • 一个函数声明会被提升到所在作用域的顶部
    foo(); //  TypeError: foo is not a function
    var a = true;
    if (a) {
        function foo(){console.log(11);} 
    } else {
        function foo(){console.log(22);} // 
    }
    // 老版本中两个foo函数会被提升,第二个foo函数会覆盖第一个,因此foo()执行结果是22;新版本中if块内仅提升函数声明,但未包含实际函数的隐藏值,因此foo()执行结果是TypeError
    

    作用域闭包

    • 闭包:内部函数可以访问外部函数作用域,并持有对原始词法作用域的引用。
    • 经典例子:循环与闭包
    for(var i=0;i<5;i++) {
        setTimeout(function timer() {
            console.log(i);
        }, i*1000);
    }
    
    • 输出五次5的原因:循环中的五个函数在每次迭代中分别定义,但它们被封闭在一个共享的全局作用域,因此共享一个i的引用。
    • 解决方法是给每次循环的函数创建一个单独的作用域
    for(var i=0;i<5;i++) {
        (function() {
            var j = i;
            setTimeout(function timer() {
                console.log(j);
            }, j*1000);
        })()
    }
    // IIFE为每次迭代创建一个新的作用域,延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代都有一个正确的变量。
    

    or

    for(let i=0;i<5;i++) { // i在每次迭代都会声明
        setTimeout(function timer() {
            console.log(i);
        }, i*1000);
    }
    

    or

    for(var i=0;i<5;i++) {
        let j = i; // 创建块级作用域
        setTimeout(function timer() {
            console.log(i);
        }, i*1000);
    }
    

    this

    • 隐式传递一个对象引用
    • 一个函数被调用时,会创建一个执行上下文,包含函数在哪里被调用,函数的调用方式,传入的参数等,this是其中的一个属性,是函数被调用时发生的绑定。this既不指向函数本身也不指向函数的词法作用域。
    • 直接调用 new dot(.) call apply bind
    function fo(){this.x='x';};
    // 直接调用
    fo(); // ()代表立即执行
    x;    // 'x' this指向全局对象window
    var fn = function(){'use strict';this.x='x';};
    x;    // ReferenceError: x is not defined,此时全局对象是undefined;
    
    // new调用
    var foo = new fo();
    foo.x; // 'x'
    
    // dot(.)调用
    var aa = {x: 'aaa',fo: fo};
    aa.x; // 'aaa'
    aa.fo; // function fo(){this.x='x';};
    aa.fo();
    aa.x; // 'x'
    
    // call
    var b = {x: 'bb'};
    b.x; // 'bb'
    b.fo(); // TypeError: b.fo is not a function
    fo.call(b);
    b.x; // 'x'
    
    // apply bind 用法和call相似,this均指向fo.call(args),args的第一个参数
    

    对象

    • javascript六种类型:string number boolean null undefined object
    • 内置对象,对象子类型:string number boolean object function array date regexp error
    • ES6增加了可计算属性名,用[]包裹,eg:
    var prefix = foo; 
    var myObject = {[prefix + 'bar']: 'hello', [prefix + 'baz']: 'world'};
    myObject['foobar']; // hello
    myObject['foobaz']; // world
    
    • 数组也是对象,但是最好用对象存储键值对,用数组存储数组下标值对;
    // 给数组定义数字形式的属性名的bug
    var myArray = ['hello', 'world', 42];
    myArray.baz = 'bar';
    myArray.length; // 3
    myArray.baz; // 'bar'
    myArray['3'] = 'test'; 
    myArray.length; // 4 
    myArray; // ['hello', 'world', 42, 'test']
    
    • 检查属性是否存在
    var myObject = { a: 2 };
    myObject['b']; // 2
    myObject.b; // 2
    'a' in myObject; // true
    myObject.hasOwnProperty('a'); // true
    // in操作符检查属性是否在对象中或prototype原型链中;hasOwnProperty仅检查属性是否在对象中。
    
    • 属性描述符 ES5
    • getOwnpropertyDescriptor查询对象的属性的属性描述符,格式:Object.getOwnpropertyDescriptor(对象名,'属性名');
    Object.getOwnPropertyDescriptor(myObject,'a');
    // Object {
    //    value: 2, 
    //    writable: true,     可写,可修改属性的值value
    //    enumerable: true,   可枚举,出现在对象属性的遍历中
    //    configurable: true  可配置,可用defineProperty修改属性描述符,删除一个属性delete myObject.a; if false,除value和writable外的特性不能被改变。
    // }
    
    • defineProperty添加一个新属性或修改一个已有属性(的属性描述符),格式:Object.defineProperty(对象名,'属性名',{});
    Object.defineProperty(myObject,'b',{
           value: 3,
           writable: true,
           configurable: true,
           enumerable: true
    }); // Object {a: 2, b: 3}
    Object.getOwnPropertyDescriptor(myObject,'b');
    // Object {value: 3, writable: true, enumerable: true, configurable: true}
    
    • 枚举属性 enumerable
    var myObject = {};
    
    Object.defineProperty(myObject,'a',{
          value: 2,
          enumerable: true
    });
    
    Object.defineProperty(myObject,'b',{
          value: 3,
          enumerable: false
    });
    
    myObject.b; // 3
    'b' in myObject; // true
    myObject.hasOwnProperty('b'); // true
    
    for(var k in myObject) {
          console.log(k+': '+myObject[k]);
    } // a: 2
    // myObject.b存在且可正常访问,但不出现在属性的遍历中
    
    myObject.propertyIsEnumerable('b'); // false
    // propertyIsEnumerable检查给定的属性名存在于对象中(不包括原型链中)&& 可枚举的
    
    Object.keys(myObject); // ['a'] 返回可枚举属性
    
    Object.getOwnPropertyNames(myObject); // ['a','b'] 返回所有属性
    
    • 数组遍历注意事项:数组上应用for..in循环时不仅包含所有数值索引,且包含所有可枚举属性;因此遍历数组最好使用传统for循环或forEach循环。ES6中新增for..of循环遍历数据结构。数组有内置的@@iterator,因此for..of可直接遍历数组,而不能直接遍历对象。P122
    var myArray = ['hello','world',42];
    myArray.baz = 'bar';
    myArray['3']='test';
    myArray; // ["hello", "world", 42, "test"]
    
    for(var k in myArray) {console.log(k,myArray[k]);}
    //  0 hello  1 world  2 42  3 test  baz bar
    
    for(vari=0;i<myArray.length;i++){console.log(i,myArray[i])}; 
    //  0 hello  1 world  2 42  3 test 
    
    myArray.forEach(function(e){console.log(e)}); 
    // hello world 42 test
    
    for(var v of myArray){console.log(v);};
    // hello world 42 test
    
    for(var v of myObject){console.log(v);};
    // TypeError: myObject[Symbol.iterator] is not a function
    

    原型prototype


    相关文章

      网友评论

        本文标题:《你不知道的JavaScript 上》读书笔记

        本文链接:https://www.haomeiwen.com/subject/mvzfsttx.html