循环与闭包
下面这段代码会输出 6 个 6 , 而不是 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);
function 的 this 指向与调用位置有关
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
}