作用域

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

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 ); // 3foo.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(); //2foo.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的区别