this、闭包、作用域
程序是怎么执行的?
传统的编译型语言,
- 先进行分词
- 构建AST(抽象语法树)
- 代码生成
JavaScript 的执行主要分为两个阶段:
代码的预编译阶段
前置阶段:
进行变量声明
变量声明进行提升,但是值为undefined
console.log(a); // undefined var a = 10;
非表达式的函数声明进行提升
fun_01(); fun_02(); // TypeError: fun_02 is not a function function fun_01(){ console.log("01"); } var fun_02 = () => { console.log("02"); }
代码的执行阶段
对于JavaScript来说,一个变量是如何被赋值和使用的,有什么区别?
var a = 10;
编译器会在当前作用域中查找是否已经有了一个a?
是:编译器会忽略这个声明,继续往下走;
不是:在当前的作用域中,生成一个新的变量,并进行命名
解释:
var a = 10;
var a = 20; // 当前作用域已经有了a变量,所以会忽略掉变量的声明
console.log(a); // 20
a = 10;
编译器会询问当前作用域中是否已经声明了一个变量a?
是:赋值为10;
不是:去上一层作用域中去寻找(作用域嵌套)
解释:
var a = 10;
function foo(){
a = 2; // 当前作用域中没有,去上一层寻找
console.log(a);
}
foo();
console.log(a);
作用域?
根据名称去找变量的规则。
词法作用域(静态作用域)?
词法作用域就是定义在词法阶段的作用域。
你写代码的时候,将变量写在哪里决定的。因此,当词法处理器处理代码时,会保持作用域不变。(evel,with)
函数作用域?
属于这个函数的全部变量,都可以在整个函数内使用。
foo(10); // 根据函数提升原则,可以正确执行。
function foo(num){
var foo; // foo函数在外侧作用域,foo变量在内层作用域,不冲突,正确声明
console.log(foo); // undefineds
foo = num; // foo = 10
console.log(foo); // 10
}
console.log(foo); // function
foo = 1;
console.log(foo); // 1
// undefineds 10 function 1
上下文?
词法作用域是在写代码的时候,或者说是在定义的时候确定的;
而动态作用域是运行时确定的。
闭包?
函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,就形成了闭包。
(执行上下文 != 词法作用域)
function numGenerrator(){
var num = 0;
return () => {
return ++num;
}
}
var getNum = numGenerrator();
for(var i=0;i<3;i++){
console.log(getNum());
}
const foo = (() => { // 自执行函数,变量foo引用内部函数
var num = 0;
return () => {
return num++;
}
})();
for(let i=0;i<10;i++){
foo(); // 这里执行了10次 num++ 操作,num的结果是9(++num的结果是10)
}
console.log(foo()); // 最终结果是10
const foo = () => {
var arr = [];
var i;
for(i = 0;i<10;i++){
arr[i] = function() {
console.log(i);
}
}
return arr[0];
}
/**
* 执行了foo(),然后执行foo()返回的函数
* 无论调用数组中的哪个函数,都会打印10
*/
foo()();
var fn = null;
const foo = () => {
var a = 2;
function innerFoo(){
console.log(a);
}
fn = innerFoo;
}
const bar = () => {
fn();
}
/**
* 最终的结果就是2
*/
foo();
bar();
闭包的使用场景?
防抖、截流 => 保存执行上下文环境
this?
根据函数执行上下文,动态决定。
- 简单调用时,this指向【window、global、undefined】(浏览器、node、严格模式)
- 对象调用时,绑定在对象上
- call、apply、bind时,绑定在指定的参数上
- 使用new关键字时,绑定到新创建的对象上
(优先级:new > apply、call、bind > 对象调用)
- 使用箭头函数,根据外层规则而决定
const foo = {
bar:10,
fn:function(){
console.log(this);
console.log(this.bar);
}
}
var fn1 = foo.fn;
// 浏览器中是window,node环境中是global
// undefined
fn1();
const person = {
name:"kaiven",
brother:{
name:"jack",
fn:function(){
return this.name;
}
}
}
console.log(person.brother.fn()); // jack
const o1 = {
text: "o1",
fn: function () {
return this.text;
}
}
const o2 = {
text: "o2",
fn: function () {
return o1.fn();
}
}
const o3 = {
text: "o3",
fn: function () {
var fn = o1.fn;
return fn();
}
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
const foo = {
name:"kaiven",
logName:function(){
console.log(this.name);
}
}
const bar = {
name:"jack"
}
/**
* logName这个函数被bar调用,所以最终的结果是jack undefined
*/
console.log(foo.logName.call(bar));
/**
* Node.js(20.13.1)
*/
var number = 5;
var obj = {
number:3,
fn1:(function(){
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function(){
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(globalThis.number);
/**
* 结果:
* NaN
* 9
* 3
* 27
* NaN
*/
/**
* Chrome(内核版本120+)
*/
var number = 5;
var obj = {
number:3,
fn1:(function(){
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function(){
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(globalThis.number);
/**
* 结果:
* 10
* 9
* 3
* 27
* 20
*/
2024.11.14
writeBy kaiven