this 指针/闭包/作用域
程序怎么执行
传统编译流程: 先进行分词 -> 构建 AST(抽象语法树)-> 生成代码
JavaScript 执行分为两个阶段: 1. 预编译阶段 - 前置阶段: - 进行变量声明 - 变量声明进行提升,但是值为 undefined - 非表达式函数进行提升 2. 执行阶段
JS 中变量是如何被赋值和使用的?有什么区别
var a = 2
var a: 编译器会先问作用域,是不是已经有了一个 a 在当前作用域中? 是:编译器忽略当前声明,继续往下走 不是:在当前作用域中声明此变量,并命名为 aa = 2编译器会先问作用域,是不是已经有了一个 a 在当前作用域中? 是:赋值为 2 不是:去上层作用域找
作用域
作用域:根据名称查找变量的规则
词法作用域/静态作用域
就是定义词法阶段的作用域,通俗来讲,就是你写代码的时候,将变量写在哪里决定的。 因此,当词法分析器处理代码时,会保持作用域不变。eval/with 除外
函数作用域
就是属于这个函数的全部变量,都可以在整个函数内使用
上下文
词法作用域是在写代码,或者定义时确定的, 动态作用域时运行时确定的
闭包
什么是闭包?
函数嵌套函数,内层函数引用了外层函数作用域下的变量,并且内层函数在全局下可访问,就形成了闭包
闭包使用场景
- 函数式编程
- 当一个函数的执行,和上下文有关时
- 防抖/节流
/** 防抖 */
function debounce(func, delay) {
let timer = null;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
/** 节流 */
function throttle(fn, delay){
let timer = null;
return function(){
const context = this;
const args = arguments;
if (!timer) {
timer = setTimeout(function(){
fn.apply(context, args);
}, delay);
}
};
};
- 保存执行环境
this
this 指向
是根据上下文动态决定的
- 在简单调用时,this指向默认为window, global, undefined(浏览器/node/严格模式)
- 对象调用时,绑定在对象上
- 使用call,bind, apply绑定在参数上
- 使用new关键字时,绑定在新创建的对象上 (以上三条优先级:new > call/bind/apply > 对象调用)
- 使用箭头函数,根据外层规则决定
var number = 5;
const obj = {
number: 3,
fn: (function(){
var number;
this.number *= 2;
number *= 2;
number = 3;
return function() {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
fn = obj.fn;
fn.call(null);
obj.fn();
console.log(number)
手动实现 call/apply/bind
/** call */
Function.prototype.myCall = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall - 被调用的对象必须是函数');
}
context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
let fn = Symbol('key');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
}
/** apply */
Function.prototype.myApply = function(context, argsArr) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myApply - 被调用的对象必须是函数');
}
if (argsArr && !Array.isArray(argsArr)) {
throw new TypeError('Function.prototype.myApply - 第二个参数必须时数组');
}
context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
let fn = Symbol('key');
context[fn] = this;
const result = Array.isArray(argsArr) ? context[fn](...argsArr) : context[fn]();
delete context[fn];
return result;
}
/** bind */
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myBind - 被调用的对象必须是函数');
}
context = context || (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ?window : undefined);
const _this = this;
return function fn(...innerArgs) {
if (this instanceof fn) {
return new _this(...args, ...innerArgs);
}
return _this.apply(context, args.concat(innerArgs));
};
}