作用域
作用域是在运行时代码中的某些特定局部中变量,函数和对象的可拜访性。换句话说,作用域决定了代码区块中变量和其余资源的可见性。
function foo() { var a = 1}foo()console.log(a) // Uncaught ReferenceError: inVariable is not defined
下面例子能够了解为:作用域最大的用途就是隔离变量,不同作用域下同名变量不会有抵触。
在JavaScript中,作用域能够分为
- 全局作用域
- 函数作用域
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为咱们提供了‘块级作用域’,可通过新增命令let和const来体现。
块级作用域
块级作用域可通过新增命令let和const申明,所申明的变量在指定块的作用域外无奈被拜访。块级作用域在如下状况被创立:
- 在一个函数外部
- 在一个代码块(由一对花括号包裹)外部
作用域链
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的参加。这里仍旧进行的是默认绑定。
- 显示绑定
call
或apply
或bind
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的区别
call
跟apply
的用法简直一样,惟一的不同就是传递的参数不同,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; // }
绑定规定优先级
- 是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象
- 是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
- 是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
- 如 果都不是的话,应用默认绑定。如果在严格模式下,就绑定到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的区别