你所不知道的javascript(1)

作用域

  • 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
复制代码

转载于:https://juejin.im/post/5d05d257e51d45554877a5c9

猜你喜欢

转载自blog.csdn.net/weixin_34327761/article/details/93173910