乐趣区

关于javascript:JS基础复习作用域this闭包

作用域

作用域是在运行时代码中的某些特定局部中变量,函数和对象的可拜访性。换句话说,作用域决定了代码区块中变量和其余资源的可见性。

function foo() {var a = 1}
foo()
console.log(a) // Uncaught ReferenceError: inVariable is not defined

下面例子能够了解为:作用域最大的用途就是隔离变量,不同作用域下同名变量不会有抵触。

在 JavaScript 中,作用域能够分为

  • 全局作用域
  • 函数作用域

ES6 之前 JavaScript 没有块级作用域, 只有全局作用域和函数作用域。ES6 的到来,为咱们提供了‘块级作用域’, 可通过新增命令 let 和 const 来体现。

块级作用域

块级作用域可通过新增命令 let 和 const 申明,所申明的变量在指定块的作用域外无奈被拜访。块级作用域在如下状况被创立:

  1. 在一个函数外部
  2. 在一个代码块(由一对花括号包裹)外部

作用域链

1. 自在变量

首先认识一下什么叫做 自在变量 。如下代码中,console.log(a) 要失去 a 变量,然而在以后的作用域中没有定义 a(可比照一下 b)。以后作用域没有定义的变量,这成为 自在变量。自在变量的值如何失去 —— 向父级作用域寻找(留神:这种说法并不谨严,下文会重点解释)。

2. 什么是作用域链

如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就发表放弃。这种一层一层的关系,就是 作用域链

let a = 'global';
console.log(a);

function course() {
    let b = 'zhaowa';
    console.log(b);

    session();
    function session() {
        let c = 'this';
        console.log(c);

        teacher();
        function teacher() {
            let d = 'yy';
            console.log(d);

            console.log('test1', b);
        }
    }
}
console.log('test2', b);
course();

if(true) {
    let e = 111;
    console.log(e);
}
console.log('test3', e)

执行上下文

许多开发人员常常混同作用域和执行上下文的概念,误认为它们是雷同的概念,但事实并非如此。

JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段, 这两个阶段所做的事并不一样:

解释阶段:

  • 词法剖析
  • 语法分析
  • 作用域规定确定

执行阶段:

  • 创立执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript 解释阶段便会确定作用域规定,因而作用域在函数定义时就曾经确定了,而不是在函数调用时确定,然而执行上下文是函数执行之前创立的。执行上下文最显著的就是 this 的指向是执行时确定的。而作用域拜访的变量是编写代码的构造确定的。

作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能扭转;作用域在定义时就确定,并且不会扭转

this

this 的绑定理论是函数被调用时才产生的绑定,也就是 this 指向什么,取决于你如何调用函数.

this 是在执行时动静读取上下文决定的,而不是创立时

this 的绑定规定

  • 默认绑定
function foo() {console.log( this.a);
}
var a = 2; 
foo(); // 2

默认绑定: 将全局对象绑定 this

留神:在严格模式下(strict mode),全局对象将无奈应用默认绑定,即执行会报 undefined 的谬误

  • 隐式绑定

除了间接对函数进行调用外,有些状况是,函数的调用是在某个对象上触发的,即调用地位上存在上下文对象。

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); // 3
  • 隐式失落(回调函数)
function foo() {console.log( this.a);
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

setTimeout(obj.foo, 100); // 2

同样的情理,尽管参传是 obj.foo,因为是援用关系,所以传参实际上传的就是 foo 对象自身的援用。对于 setTimeout 的调用,还是 setTimeout -> 获取参数中 foo 的援用参数 -> 执行 foo 函数,两头没有 obj 的参加。这里仍旧进行的是默认绑定。

  • 显示绑定

callapplybind

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

var a = 2;

var obj1 = {a: 3,};

var obj2 = {a: 4,};
foo.call(obj1); // 3
foo.call(obj2); // 4

call、apply、bind 的区别

callapply 的用法简直一样,惟一的不同就是传递的参数不同,call只能一个参数一个参数的传入。
apply则只反对传入一个数组,哪怕是一个参数也要是数组模式。最终调用函数时候这个数组会拆成一个个参数别离传入。

至于 bind 办法,他是间接扭转这个函数的 this 指向并且返回一个新的函数,之后再次调用这个函数的时候 this 都是指向 bind 绑定的第一个参数。bind传餐形式跟 call 办法统一。

手写 bind

Function.prototype.myBind = function() {
    const _this = this
    // 复制参数
    const args = Array.prototype.slice.call(arguments);
    const newThis = args.shift();
    
    
    return function() {return _this.apply(newThis, args)    
    }
}

手写 apply

Function.prototype.myApply = function(context) {
    // 参数检测
    context = context || window;
    
    // 指向挂载函数
    context.fn = this;
    
    // 
}

绑定规定优先级

  1. 是否在 new 中调用(new 绑定)? 如果是的话 this 绑定的是新创建的对象
  2. 是否通过 call、apply(显式绑定)或者硬绑定调用? 如果是的话,this 绑定的是 指定的对象。
  3. 是否在某个上下文对象中调用(隐式绑定)? 如果是的话,this 绑定的是那个上下文对象。
  4. 如 果都不是的话, 应用默认绑定。如果在严格模式下, 就绑定到 undefined, 否则绑定到 全局对象。

规定例外

function foo() {     console.log( this.a);}foo.call(null); // 2foo.call(undefined); // 2

箭头函数

var foo = () => {console.log( this.a);
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); //2
foo.call(obj); //2,箭头函数中显示绑定不会失效
function foo(){return () => {console.log( this.a);
    }    
}



var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo();
bar(); //3

闭包

什么是闭包?

一个函数和对其四周状态(lexical environment,词法环境 )的援用捆绑在一起(或者说函数被援用突围),这样的组合就是 闭包closure)。也就是说,闭包让你能够在一个内层函数中拜访到其外层函数的作用域。在 JavaScript 中,每当创立一个函数,闭包就会在函数创立的同时被创立进去。

闭包场景

  • 函数作为返回值的场景
 function mail() {
     let content = '信';
     return function() {console.log(content);
     }
 }
const envelop = mail();
envelop();
  • 函数作为参数
// 繁多职责
let content;
// 通用存储
function envelop(fn) {
    content = 1;

    fn();}

// 业务逻辑
function mail() {console.log(content);
}

envelop(mail);
  • 函数嵌套
let counter = 0;

function outerFn() {function innerFn() {
        counter++;
        console.log(counter);
        // ...
    }
    return innerFn;
}
outerFn()();
  • 事件执行
let lis = document.getElementsByTagName('li');

for(var i = 0; i < lis.length; i++) {(function(i) {lis[i].onclick = function() {console.log(i);
        }
    })(i);
}

参考文章

  • 彻底搞懂 JS 中 this 机制
  • 深刻了解 JavaScript 作用域和作用域链
  • 聊一聊 call、apply、bind 的区别
退出移动版