作用域
- 1.作用域:根据名称查找变量的一套规则。
- 2.注意:当对变量进行RHS查询时,一直到最外层的作用域都无法找到该变量。则会抛出ReferenceError的异常类型。但当对变量进行LHS查询时,直到顶层作用域都无法找到,在非严格模式下讲会在全局作用域中创建一个改该变量。
词法作用域
- 1.定义在词法阶段的作用域
- 2.eval()和with会欺骗词法作用域并且会影响性能。
函数作用域和块级作用域
- 1.如果function是声明中的第一个在词,那么就是一个函数声明,否则就是一个函数表达式。
- 2.因为在作用域中变量有被覆盖的可能,所以引入了函数作用域的概念,以防止变量被污染。但是这样函数的名称污染了全局作用域,其次必须要调用函数才能运行其中的代码。所以衍生出了立即执行函数的概念来进行优化。
- 3.var是全局作用域或者函数级作用域,所以会有变量被污染的情况出现。所以引入了let这种块级作用域。
- var会有变量提升的情况出现。let没有提升的情况出现。
- 当块级作用域完成它的使命之后,垃圾回收机制就会把它清理走。而全局作用域下却无法。
提升
- 1.函数也是可以被提升的。
- 2.函数表达式不可以被提升。
foo();//不是ReferenceError,而是TypeError!
var foo=function bar(){};
复制代码
闭包
我自己的理解:闭包就是让你拿到你本不该拿到的值。不是自己词法作用域里的值。 有回调函数的一定是闭包。
万恶的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 居然是0次?
复制代码
上面的例子充分说明了this并不指向它本身。 call()可以帮助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.call(foo,i);
}
}
// foo:6
// foo:7
// foo:8
// foo:9
console.log(foo.count);//4
复制代码
this也不指向函数的词法作用域。
function foo(){
var a=2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo();//a is not defined
复制代码
this只取决于函数的调用位置
调用位置如何分析请看下面的例子。
function baz(){
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log("baz");
bar();//bar的调用位置
}
function bar(){
// 当前调用栈是baz-->bar
// 因此,当前调用位置在baz中
console.log("baz");
foo();
}
function foo(){
// 当前调用栈是baz-->bar-->foo
// 因此,当前调用位置在bar中
console.log("foo");
}
baz();// baz的调用位置
复制代码
this默认绑定规则
function foo(){
console.log(this.a);
}
var a=2;
foo();//2
复制代码
如果使用严格模式,则不能将全局对象用语默认绑定,因此this会绑定到undefined。
function foo(){
"use strict";
console.log(this.a);
}
var a=2;
foo();// this is undefined
复制代码
隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this绑定到obj,因此this.a和obj.a是一样的。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
};
obj.foo();//2
复制代码
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
function foo(){
console.log(this.a);
}
var obj2={
a:42,
foo:foo
};
var obj1={
a:2,
obj2:obj2
};
obj1.obj2.foo(); // 42
复制代码
隐式丢失
一个最常见的this绑定是被隐式绑定的函数会丢失绑定对象,也就是它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
};
var bar=obj.foo;
var a="oops,global";
bar();// oops,global
复制代码
- 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo(){
console.log(this.a);
}
function doFoo(fn){
// fn其实引用的是foo
fn();// 调用位置
}
var obj={
a:2,
foo:foo
};
var a="oops,global";
doFoo(obj.foo);//oops,global
复制代码
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
};
var a="oops,global";
setTimeout(obj.foo,100);//oops,global
复制代码
显示绑定
call()、apply()、bind()都会改变this指向
function foo(){
console.log(this.a);
}
var obj={
a:2,
};
foo.call(obj);//2
复制代码
通过foo.call(...)可以在调用foo时强制把它的this绑定到obj上。
function foo(){
console.log(this.a);
}
var obj={
a:2
};
var bar=function(){
foo.call(obj);
};
bar();; //2
setTimeout(bar,100); //2
bar.call(window); //2 硬绑定的bar不可能再修改它的this
复制代码
无论之后如何调用bar,它总会手动在obj上调用foo。
function foo(el){
console.log(el,this.id);
}
var obj={
id:'awesome'
};
[1,2,3].forEach(foo,obj);
// 调用foo时把this绑定到obj上。
// 1 awesome 2 awesome 3 awesome
复制代码
new绑定
构造函数相当于创建了一个新对象。this指向新对象。
function foo(a){
this.a=a;
}
var bar=new foo(2);
console.log(bar.a); //2
复制代码