作用域
作用域是在运行时代码中的某些特定局部中变量,函数和对象的可拜访性。换句话说,作用域决定了代码区块中变量和其余资源的可见性。
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); // 3
foo.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(); //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 的区别