《你不知道的JS-上卷》学习笔记

266 阅读5分钟

第一部分·作用域和闭包

1.LHS和RHS引用

  • 官方定义:赋值操作的左侧和右侧。赋值操作不一定是=
    • LHS:查找赋值操作的对象,即变量赋值并存入内存。
    • RHS:取源值,即从内存中读取。
  • 思考以下代码:
function foo(a){ // 对a进行LHS
    console.log(a); // 对a进行RHS
}
foo(2); // 对foo执行RHS引用
  • 为什么要区分LHS和RHS(以下是非严格模式下区别)
    • LHS引用在作用域中找不到变量,那就会在全局作用域中声明一个。
    • RHS引用在作用域中找不到变量,那就会报错ReferenceError。
  • 思考如下代码:
function foo(a){
    console.log(a+b);
    b = a;
}
foo(2);
function foo(a){ 
    console.log(a+b); // 对a,b进行RHS
    b = a; // 对a进行RHS,对b进行LHS
}
foo(2);

2.词法作用域

  • 定义:定义在词法阶段的作用域。
  • 注意:变量的查找过程是由内而外查找,直到找到为止,若直到全局还没找到会报错。因此破坏词法作用域会降低性能。
  • 破坏词法作用域的方法(仅限于在非严格模式下)

    不推荐,而且会降低可读性,降低行能。

    • eval()--严格模式下eval会有自己的词法作用域,不会影响其他的 思考如下代码:
    function foo(str){
        eval(str);
        console.log(a); // 2
    }
    foo("var a = 2")
    
    • with 思考如下代码:
    function foo(obj){
        with(obj){
            a = 2;
        }
    }
    var o1 = { a: 3 };
    foo(o1);
    console.log(o1.a); // 2
    
    var o2 = { b: 3 };
    console.log(o2.a); // undifined
    console.log(a); // 2,a被泄漏到了全局
    
    with 会生成自己的词法作用域,但是其内部 a=2 也只是对 a 进行了LHS引用,当找不到时会在全局声明,所以o2就泄露到了全局。

3.函数作用域

  • IIFE匿名自执行函数

4.块级作用域

  • with
  • try/catch中catch会创建块级作用域
  • let

5.提升

  • 函数和变量声明会先执行,这种现象叫做函数/变量声明提升。需要注意的是:如果函数名和变量名重复,函数名会优先

6.闭包

  • 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用 域之外执行。

7.动态作用域

  • 与词法作用域区别:
    • 词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。(this)
    • 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用

8.块作用域的替代方案

  • es6之前的块作用域是通过try/catch来实现的,包括将es6的块级作用域转换成es5。
  • 参考以下代码,理解es6转换为es5
// es6
{
    let a = 2;
    console.log(a); // 2
}
console.log(a); // ReferenceError

// es5
try{
    throw 2;
}catch(a){
    console.log(a); // 2
}
console.log(a); // ReferenceError

第二部分·this和对象原型

1.关于this

  • 思考一下代码输出结果,如何修改能达到预期结果
function foo(num){
    console.log('foo:'+num);
    this.count++;
}
foo.count = 0;
var i;
for(i=0;i<10;i++){
    if(i>5){
        foo(i);
    }
}
// foo:6  foo:7  foo:8  foo:9
console.log(foo.count); // 0

2.this全面解析

  • 绑定规则
    • 默认绑定:无法应用其他规则时
    function foo(){
        console.log(this.a)
    }
    var a = 2;
    foo(); // 2
    
    • 隐式绑定: 调用位置是否有上下文对象, 或者说是否被某个对象拥有或者包 含(this指向直接调用它的对象)
    function foo(){
        console.log(this.a)
    }
    var obj = {
        a:2,
        foo:foo
    }
    obj.foo(); // 2
    
    • 隐式丢失
    function foo(){
        cosole.log(this.a)
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var bar = obj.foo;
    var a = "oops, gobal"
    bar(); // oops, gobal
    
    然 bar 是 obj.foo 的一个引用, 但是实际上, 它引用的是 foo 函数本身, 因此,此时的bar() 其实是一个不带任何修饰的函数调用, 因此应用了默认绑定。
    • 显示绑定:call()、apply()、bind()
    function foo() {
        console.log(this.a)
    }
    var obj = {
        a: 2
    }
    foo.call(obj); // 2
    
    • API调用上下文: 第三方库的许多函数, 以及 JavaScript 语言和宿主环境中许多新的内置函数, 都提供了一个可选的参数, 通常被称为“上下文”(context), 其作用和 bind(..) 一样, 确保你的回调函数使用指定的 this。实际上是用call和apply实现了绑定
     function foo(el){
        console.log(el, el.this.id)
     }
     var obj = {
        id: "awesome"
     }
     [1,2,3].forEach(foo, obj);
    
    • new绑定:this指向new出来的实例化对象
    function foo(val){
        this.a = val;
    }
    var bar = new foo(2);
    console.log(bar.a); // 2
    
  • 优先级:显示绑定 > 隐式绑定
  • 间接引用
    function foo(){
        console.log(this.a)
    };
    var a = 2;
    var o = {a:3,foo:foo};
    var p= {a:4};
    o.foo(); // 3
    (p.foo = o.foo)(); // 2
    
    赋值表达式(p.foo = o.foo)()返回值是目标函数的引用,因此调用位置是foo(),而不是p.foo()或者o.foo()。因此这里使用默认绑定;

3.对象

  • 语法

注 1: 原理是这样的, 不同的对象在底层都表示为二进制, 在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0, 自然前三位也是 0, 所以执行 typeof 时会返回“object”。

  • 属性描述符
    • writable: 可写
    • enumerable: 可枚举, “可枚举” 就相当于“可以出现在对象属性的遍历中”。
    let myObject = {};
    myObject.defineproperty(myObject, 'a', {value:2, enumerable: false});
    myObject.defineproperty(myObject, 'b', {value: 3, enumetable: true});
    for(let key in myObject){
        console.log(key, myObject[key]); // 'b'  3
    }
    
    • configurable: 可配置
  • 对象方法
    • Object.defineproperty:可以修改属性描述符, 注意: 如你所见, 把 configurable 修改成false 是单向操作, 无法撤销!
    • Object.preventExtensions:禁止一个对象添加新属性且保留已有属性
    • Object.seal密封一个对象,即对该对象调用Object.preventExtensions并且把所有现有属性标记为configurable: false
    • Object.freeze:创建一个冻结对象,即对该对象执行Object.seal并且把所有现有属性标记为writable: false

4.原型

  • [[Prototype]] 在查找对象属性的时候,如果对象本身没有找到,那就会根据他的原型链一直向上查找,知道Object.property

for...in循环也是,如果只想查找对象本身,就需要hasOwnProperty()判断