js中声明提高、做用域(链)、`this`关键字和箭头函数

1.关于声明提早

  • js中,容许变量使用在声明以前,不过此时为undefined
console.log(a); // undefined
var a = 1;
复制代码
  • 变量无论在哪里声明,都会在任意代码执行前处理。在es5 strict mode,赋值给未声明的变量将报错。
  • 显式声明:带有关键字 var 的声明,做用域就是当前执行上下文,即某个函数,或者全局做用域(声明在函数外,即变量会挂在在window对象上)
  • 隐式声明:若是一个变量没有使用var声明,window便拥有了该属性,所以这个变量的做用域不属于某一个函数体,而是window对象。
function varscope(){
    foo = "I'm in function"; //直接赋值 没有声明
    console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function
复制代码
  • 关于声明提早的例子
function testOrder(arg) {
    console.log(arg); // arg是形参,不会被从新定义
    console.log(a); // 由于函数声明比变量声明优先级高,因此这里a是函数
    var arg = 'hello'; // var arg;变量声明被忽略, arg = 'hello'被执行
    var a = 10; // var a;被忽视; a = 10被执行,a变成number
    function a() {
        console.log('fun');
    } // 被提高到做用域顶部
    console.log(a); // 输出10
    console.log(arg); // 输出hello
}; 
testOrder('hi');
/* 输出: hi function a() { console.log('fun'); } 10 hello */
复制代码

2.关于做用域

  • 函数做用域javascript

    函数做用域内,对外是封闭的,从外层的做用域没法直接访问函数内部的做用域html

    function bar() {
      var testValue = 'inner';
    }
    console.log(testValue);	// 报错:ReferenceError: testValue is not defined
    复制代码

    经过 return 访问函数内部变量:java

    function bar(value) {
      var testValue = 'inner';
      return testValue + value;
    }
    console.log(bar('fun'));// "innerfun"
    复制代码

    经过 闭包 访问函数内部变量:闭包

    function bar(value) {
      var testValue = 'inner';
      var rusult = testValue + value;
      function innser() {
        return rusult;
      };
      return innser();
    }
    console.log(bar('fun'));		// "innerfun"
    复制代码
  • 当即执行函数做用域app

    这是个很实用的函数,不少库都用它分离全局做用域,造成一个单独的函数做用域;它可以自动执行(function() { //... })()里面包裹的内容,可以很好地消除全局变量的影响;函数

    <script type="text/javascript"> (function() { var testValue = 123; var testFunc = function () { console.log('just test'); }; })(); console.log(window.testValue); // undefined console.log(window.testFunc); // undefined </script>
    复制代码
  • 块级做用域ui

    在 ES6 以前,是没有块级做用域的概念的。this

    for(var i = 0; i < 5; i++) {
      // ...
    }
    console.log(i)				// 5
    复制代码

    很明显,用 var 关键字声明的变量,在 for 循环以后仍然被保存这个做用域里;es5

    这能够说明: for() { }仍然在,全局做用域里,并无产生像函数做用域同样的封闭效果;spa

    若是想要实现 块级做用域 那么咱们须要用 let 关键字声明

    for(let i = 0; i < 5; i++) {
      // ...
    }
    console.log(i)				// 报错:ReferenceError: i is not defined
    复制代码

    在 for 循环执行完毕以后 i 变量就被释放了,它已经消失了!!!

    一样能造成块级做用域的还有 const 关键字:

    if (true) {
      const a = 'inner';
    }
    console.log(a);				// 报错:ReferenceError: a is not defined
    复制代码

    let 和 const 关键字,建立块级做用域的条件是必须有一个 { } 包裹:

  • 词法做用域

    当咱们要使用声明的变量时:JS引擎总会从最近的一个域,向外层域查找

    testValue = 'outer';
    function afun() {
      var testValue = 'middle';
      console.log(testValue);// "middle"
      function innerFun() {
        var testValue = 'inner';
        console.log(testValue);// "inner"
      }
      return innerFun();
    }
    afun();
    console.log(testValue);	// "outer"
    复制代码

    当 JS 引擎查找变量时,发现全局的 testValue 离得更近一些,则取全局的testValue的值即 outer

    var testValue = 'outer';
    function foo() {
      console.log(testValue);// "outer"
    }
    
    function bar() {
      var testValue = 'inner';
      foo();
    }
    bar();
    复制代码
  • 动态做用域

    动态做用域,做用域是基于调用栈的,而不是代码中的做用域嵌套;

    做用域嵌套,有词法做用域同样的特性,查找变量时,老是寻找最近的做用域;

3.关于this关键字

在一个函数中,this老是指向当前函数的全部者对象,this老是在运行时才能肯定其具体的指向, 也才能知道它的调用对象。

window.name = "window";
function f(){
    console.log(this.name);
}
f();//window

var obj = {name:'obj'};
f.call(obj); //obj
复制代码

在执行f()时,此时f()的调用者是window对象,所以输出”window”

f.call(obj) 是把f()放在obj对象上执行,至关于obj.f(),此时f中的this就是obj,因此输出的是”obj”

对比如下两段代码:

var foo = "window";
var obj = {
    foo : "obj",
    getFoo : function(){
        return function(){
            return this.foo;
        };
    }
};
var f = obj.getFoo(); 
f(); //输出'window'

/* 分析 执行var f = obj.getFoo()返回的是一个匿名函数,至关于: var f = function(){ return this.foo; } f() 至关于window.f(), 所以f中的this指向的是window对象,this.foo至关于window.foo, 因此f()返回"window" */
复制代码
var foo = "window";
var obj = {
    foo : "obj",
    getFoo : function(){
        var that = this;
        return function(){
            return that.foo;
        };
    }
};
var f = obj.getFoo();
f(); //输出'obj'

/* 分析 执行var f = obj.getFoo() 一样返回匿名函数,即: var f = function(){ return that.foo; } 惟一不一样的是f中的this变成了that, 要知道that是哪一个对象以前,先肯定f的做用域链:f->getFoo->window 并在该链条上查找that,此时能够发现that指代的是getFoo中的this, getFoo中的this指向其运行时的调用者,从var f = obj.getFoo() 可知此时this指向的是obj对象,所以that.foo 就至关于obj.foo,因此f()返回"obj" */
复制代码

4.关于箭头函数

箭头函数有两种格式:

var fn = x => x * x; //只包含一个表达式,连{ ... }和return都省略掉了

x => { //还有一种能够包含多条语句,这时候就不能省略{ ... }和return:
    if (x > 0) {
        return x * x;
    }
    else {
        return - x * x;
    }
}
复制代码

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法做用域,由上下文肯定。

对比如下两个例子

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth;
        };
        return fn();
    }
};
复制代码
//箭头函数彻底修复了this的指向,this老是指向词法做用域,也就是外层调用者obj:
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 29

//因为this在箭头函数中已经按照词法做用域绑定了,因此,用call()或者apply()调用箭头函数时,没法对this进行绑定,即传入的第一个参数被忽略:
var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth还是1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2015); // 25
复制代码

箭头函数与this结合例子

var name = 'window'

var person1 = {
  name: 'person1',
  show1: function () {
    console.log(this.name)
  },
  show2: () => console.log(this.name),
  show3: function () { 
    return function () {
      console.log(this.name)
    }
  },
  show4: function () {
    return () => console.log(this.name)
  }
}
var person2 = { name: 'person2' }

person1.show1() //person1
person1.show1.call(person2) //person2

person1.show2() //window
person1.show2.call(person2) //window

person1.show3()() //window 
/*person1.show3是一个高阶函数,它返回了一个函数,分步走的话,应该是这样: var func = person3.show() func() 从而致使最终调用函数的执行环境是window,但并非window对象调用了它。因此说,this老是指向调用该函数的对象,这句话还得补充一句:在全局函数中,this等于window。 */


person1.show3().call(person2)//person2 经过person2调用了最终的打印方法
person1.show3.call(person2)()//window 先经过person2调用了person1的高阶函数,而后再在全局环境中执行了该打印方法。

person1.show4()() //person1 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
person1.show4().call(person2) //person1 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用person2去调用这个箭头函数,它指向的仍是person1。
person1.show4.call(person2)() //person2 箭头函数的this指向的是谁调用箭头函数的外层function,箭头函数的this就是指向该对象,若是箭头函数没有外层函数,则指向window
复制代码

例题:

var number = 5;
var obj = {
    number: 3,
    fn1: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
复制代码

输出10 9 3 27 20

相关文章
相关标签/搜索