作用域和上下文、闭包
作用域
在特定场景下、特定范围内,查找变量的一套规则。一般情况下特指:静态作用域、词法作用域,即代码层面上的。
从作用范围分类:
- 全局作用域
- 函数作用域:在函数内声明的所有变量,在函数体内是始终可见的,可以在整个函数范围内复用。
- 块作用域:是一个用来对之前的最小授权原则进行扩展的工具,将代码在函数中隐藏信息扩展为在块中。
什么会构成块级作用域:if、for、{…}。
当let作为块级作用域时,会存在暂时性死区。
暂时性死区的范围:从let声明的变量的块的第一行,到声明变量之间的这个区域,被称为暂时性死区。暂时性死区存在时,会让let绑定这个区域,在这个区域内,无法执行该变量的其他声明。
函数表达式
代码的预编译阶段:
- 对变量的内存空间进行分配;
- 对变量声明进行提升,但是值为undefined;
- 对所有非表达式的声明进行提升。
执行上下文
执行上下文:
作用域关注的函数生命在何处,而上下文主要关注的是函数从何处开始调用。
闭包
定义:函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,这样便形成了闭包。(简单理解:当函数的执行上下文没有在原本的词法作用域内,就形成了闭包)。
闭包常见的场景:函数式编程、window-event handler、setTimeout。
this
this的使用规则
this的指向:this的指向是根据执行上下文动态决定的。
- 在简单调用时,this默认指向的是window、global、undefined,分别对应浏览器端、node端、严格模式。
- 对象调用时,绑定在对象上。
- 使用call/apply/bind时,绑定在指定的参数上。
- 使用new关键字,通过构造函数调用创建对象上。
- 以上2、3、4优先级:new>call/apply/bind>对象调用。
- 使用箭头函数时,根据外层的规则决定this的指向。
this例题分析
var number = 5;//5-10-20
var obj = {
number: 3,
//fn1是立即执行函数,在JS解析时会执行
fn1: (function() {
var number;//持久化变量-3-9-27
this.number *= 2;//全局定义的number会变成10
number = number * 2;
number = 3;//此处赋值为3,上述number*2可不管
return function() {
var num = this.number;
this.number *= 2;//使全局的number变为20
console.log(num);//【10】——【3】
number *= 3;//3*3=9,9*3=27
console.log(number);//【9】——【27】
}
})();
}
var fn1 = obj.fn1;//无console
fn1.call(null); // fn1();//【10】【9】
obj.fn1();//【3】【27】
console.log(window.number);//【20】
// 10, 9, 3, 27 ,20
什么时候需要考虑this的指向问题?
- 函数式编程
- 闭包
- …
bind的实现
- 在不考虑new的优先级
Function.prototype.bind = Function.prototype.bind || function(context) {
const fn = this;
const args = Array.prototype.slice.call(arguments, 1);
return function(...innerArgs) {
const allArgs = [...args, ...innerArgs];
return fn.apply(context, allArgs);
}
}
//test
function foo() {
this.baz = "bar";
return this.baz;
}
var func = foo.bind({baz:"baz"});
func() // "baz";
new func(); // baz: bar
- 考虑new的优先级:bind 返回的函数如果作为构造函数搭配 new 关键字出现的话,这种绑定就需要被忽略,this要绑定在实例上,也就是说,new 操作符要高于bind 绑定。
Function.prototype.bind = Function.prototype.bind || function(context) {
const fn = this;
const args = Array.prototype.slice.call(arguments, 1);
var F = function() {};
F.prototype = this.prototype;
var bound = function() {
var innerArgs = Array.prototype.slice.call(arguments);
const allArgs = [...args, ...innerArgs];
// 如果存在new, 绑定的对象不一样
return fn.apply(this instanceof F ? this : context || this, allArgs);
}
bound.prototype = new F();
return bound;
}
//但没有修复length属性
- 终版:满足标准的 es5-shim
function bind(that) {
var target = this;
if (!isCallable(target)) {
throw new TypeError('Function.prototype.bind called on incompatible ' + target);
}
var args = array_slice.call(arguments, 1);
var bound;
var binder = function () {
if (this instanceof bound) {
var result = target.apply(
this,
array_concat.call(args, array_slice.call(arguments))
);
if ($Object(result) === result) {
return result;
}
return this;
} else {
return target.apply(
that,
array_concat.call(args, array_slice.call(arguments))
);
}
};
var boundLength = max(0, target.length - args.length);
var boundArgs = [];
for (var i = 0; i < boundLength; i++) {
array_push.call(boundArgs, '$' + i);
}
bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
Empty.prototype = null;
}
return bound;
}
实现一个call函数
function called(context) {
const args = Array.prototype.slice.call(arguments, 1);
//
context.fn = this;
if(context) {
const result = context.fn(...args);
delete context.fn;
return result;
}else {
return this(...args);
}
}