关于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的区别

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理