《你不知道的Javascript(上)》读书笔记

112 阅读2分钟

循环与闭包

下面这段代码会输出 66 , 而不是 1 2 3 4 5 6

for (var i = 0; i < 6; i++) {
    setTimeout(function () {
        console.log(i);
    }, 0);
}

原因在于每个函数都被封装在同一个作用域中, 共享同一个 i

解决办法是为每个函数加上独立的作用域和独立的变量 i

for (var i = 0; i < 6; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 0);
    })(i);
}

也可以使用 ES6 里的 let 简化代码

let 为每个块级作用域声明了独立的变量

for (let i = 0; i < 6; i++) {
    setTimeout(function () {
        console.log(i);
    }, 0);
}

模块

var abc = (function () {
    function doABC() {}

    return { doABC };
})();

abc.doABC();

var

函数具有自己的作用域

var a = 10;

// 函数里声明的 var 只会对函数作用域产生影响
function foo() {
    var a = 1;
    console.log(a); // 1
}

foo();

console.log(a); // 10
var a = 10;

function foo() {
    a = 1; // 这里是赋值而不是声明, 对外部变量产生了污染
    console.log(a); // 1
}

foo();

console.log(a); // 1

由于函数具有自己的作用域, 因此下面的代码 bar 里的 var 无法对全局的 var 产生影响, 因此 foo 里的 a 依旧是全局的 a=2

function foo() {
    console.log(a); // 2
}

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar();

let 不同, var 没有块级作用域

var a = 10;

{
    var a = 1; // 这里相当于赋值(重复声明被覆盖)
    console.log(a); // 1
}

console.log(a); // 1
let a = 10;

{
    let a = 1; // 不会影响全局的 a
    console.log(a); // 1
}

console.log(a); // 10

this

var obj = {
    id: 'obj',
    getId: function () {
        console.log(this.id);
    },
};

var id = 'window';

obj.getId(); // obj

setTimeout(obj.getId, 0); // whidow

setTimeout(obj.getId, 0); 相当于

setTimeout(function () {
    console.log(this.id);
}, 0);

setTimeout 里的回调函数的 this 指向 setTimeout 所在作用域(上面的代码为 window

固定 this 指向

bind 绑定 this

var obj = {
    id: 'obj',
    getId: function () {
        console.log(this.id);
    },
};

var id = 'window';

setTimeout(obj.getId.bind(obj), 0);

使用匿名函数

var obj = {
    id: 'obj',
    getId() {
        console.log(this.id);
    },
};

var id = 'window';

setTimeout(function () {
    obj.getId();
}, 0);

// 这样也行
setTimeout(() => {
    obj.getId();
}, 0);

使用箭头函数

var obj = {
    id: 'obj',
    getId() {
        return () => {
            console.log(this.id);
        };
    },
};

var id = 'window';

setTimeout(obj.getId(), 0);

functionthis 指向与调用位置有关

function foo() {
    console.log(this.a);
}

var obj1 = {
    a: 1,
    foo: foo,
};

var obj2 = {
    a: 2,
    obj1: obj1,
};

obj2.obj1.foo(); // 1 
// obj1 调用的 foo, 因此 this 指向 obj

var a = 3;

foo(); // 3

// foo() 相当于 window.foo();

this 丢失

var obj = {
    a: 10,
    foo() {
        console.log(this.a);
    },
};

obj.foo(); // 10

var f = obj.foo;

f(); // undefined

// 这里相当于 var foo = function() { ... }
// 即把 obj.foo 的函数体赋值给 f
// 不再和 obj 有关, 而调用它的是 window 因此不存在 a

下面代码一个道理

var obj = {
    a: 10,
    foo: function() {
        console.log(this.a);
    },
};

function setTimeout(fn, delay = 0) {
    // 相当于 fn = obj.foo
    // fn 的调用前面没有任何对象
    // 因此属于默认绑定(this 指向 window)
    fn();
}

setTimeout(obj.foo); // undefined

注意箭头函数在对象中的 this 指向全局

var obj = {
    id: 'obj',
    getId: () => { // 箭头函数被定义的作用域当前为全局
        console.log(this.id);
    },
};

var id = 'window';

setTimeout(obj.getId, 0); // window

强绑定

使用 bind 绑定了一次后 call 就不能改变 this

function foo() {
    console.log(this.id);
}

var obj1 = { id: 'obj1' };
var obj2 = { id: 'obj2' };

var f1 = foo.bind(obj1);

f1(); // obj1

f1.bind(obj2);

f1(); // obj1
f1.call(obj2); // obj1

// 当然原函数还是可以重新绑定的
var f2 = foo.bind(obj2);

软绑定

可以使用 call 改变指向

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function (obj) {
        var fn = this;

        // 删除第一个参数(obj)
        var curried = [].slice.call(arguments, 1);

        // 返回
        var bound = function bound() {
            return fn.apply(
                // this 不存在或者 this 是 window/global 就把 this 绑定到 obj 上
                // this 绑定过则指向不变(这也是绑定一次就不能再调用 bind 绑定其他的原因)
                !this || this === (window || global) ? obj : this,
                curried.concat.apply(curried, arguments)
            );
        };

        bound.prototype = Object.create(fn.prototype);
        return bound;
    };
}

function foo() {
    console.log(this.id);
}

var obj1 = { id: 'obj1' };
var obj2 = { id: 'obj2' };

var f1 = foo.softBind(obj1);

f1(); // obj1

f1.call(obj2); // obj2

迭代器

var obj = {
    name: '小明',
    age: 10,
};

Object.defineProperty(obj, Symbol.iterator, {
    enumerable: false,
    writable: false,
    configurable: true,
    value: function () {
        var o = this;
        var idx = 0;
        var ks = Object.keys(o);
        return {
            next: function () {
                return {
                    value: o[ks[idx++]],
                    done: idx > ks.length,
                };
            },
        };
    },
});

for (let v of obj) {
    console.log(v);
}

for...in...

for/in 会查找整个原型链上的属性

var obj = {
    name: '小明',
};

var o = {};

o.__proto__ = obj;

for (let v in o) {
    console.log(v); // name
}