用JavaScript写构造函数以及写箭头函数时我遇到了这个疑惑,于是特意研究了一番,总结如下:
一.作用域分类
首先,作用域分为全局和局部作用域()
var a = 'apple'; //全局变量
function fighting(){
var a = 'angel'; //局部变量
}
console.log(a); // 输出apple
其次,还有块级作用域的概念:(from ChatGPT)
块级作用域是指由一对花括号({}
)括起来的代码块内部的范围。在块级作用域中声明的变量只在该代码块内部可见,超出该代码块就无法访问。这与函数级作用域有所不同,函数级作用域是指变量在整个函数体内都是可见的。
在以下代码中,初学者很容易被迷惑,误认为由于if所在的判断代码块为true因此输出angel,实际上并不是这样,是由于var a = 'angel'作为局部变量,覆盖了if所在的块级作用域,导致console.log打印出来的是它最后声明的变量,因此无论是true还是非true,打印出来的都将是a。
//类似的for,while循环语句中也有这个问题
var a = 'apple';
if(true){
var a = 'angel';
}
console.log(a); //输出angel
为了优化这个问题,ES6推出了let和const作为声明关键字,它们具有更严格的作用域规则。
let
和 const
关键字可以创建块级作用域的变量,而使用 var
则会创建函数级作用域的变量。上面的代码中如果我们想打印apple,可以将if代码块中的var a = 'angel'; 改成 let a = 'angel'
同时,当存在多个变量时,还存在作用域链这个概念
如果说作用域是一个区域,那么作用域链就是表示次序的,遵循的是从里到外的顺序,函数内部没有声明变量,那就去函数的外部寻找
var a = 'apple';
var b = 'boy';
function fighting(){
var a = 'angel';
console.log(a); //angel
console.log(b); //boy
}
fighting();
console.log(a); //apple
二.变量提升(hoisting)
var a = 'apple';
function fighting(){
console.log(a); //undefined
var a = 'angel';
console.log(a); //angel
}
fighting();
为什么第一个打印出的是undefined?(讲解来自ChatGPT)
这是因为 JavaScript 中存在变量提升(hoisting)的机制。在你的代码中,函数中的变量声明会被提升到函数的顶部,但赋值操作不会随之提升。因此,代码的执行顺序实际上是这样的:
var a = 'apple';
function fighting() {
var a; // 变量声明被提升,但赋值操作不会提升
console.log(a); // 输出 undefined,因为此时 a 还没有被赋值
a = 'angel'; // 赋值操作
console.log(a); // 输出 'angel'
}
fighting(); // 调用函数
如果有函数声明,则比变量声明优先级更高
三.this指向
理解了以上概念,可以说说箭头函数中this的指向问题吧。
Js规定:
箭头函数体内的this
对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
在普通函数里:我们可以得知,sayHello这个方法是定义在A对象中的,当我们使用call方法,把其指向B对象,最后输出了B;可以得出,sayHello的this只跟使用时的对象有关。
var name = 'window'; // 其实是window.name = 'window'
var A = {
name: 'A',
sayHello: function(){
console.log(this.name)
}
}
A.sayHello();// 输出A
var B = {
name: 'B'
}
A.sayHello.call(B);//输出B
A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window
那如果我将sayHello那块的普通函数改成箭头函数呢?A.sayHello()输出的就是window了。
原因是sayHello的箭头函数所在的作用域是最外层的js环境,没有被其他函数包裹,且最外层的js环境指向的对象是window对象,因此this指向的是window。
var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello();// 这里输出的是window,而不是A了
如果想永远绑定A的话,代码如下:
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () =>
console.log(this.name)
return s //返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello(); //输出A
var B = {
name:'B' ;
}
sayHello.call(B); //输出A
sayHello.call(); //输出A
因此我们再总结一下:
该函数所在的作用域:箭头函数s 所在的作用域是sayHello,因为sayHello是一个函数。
作用域指向的对象:A.sayHello指向的对象是A。
好吧,bind,call,apply方法我也不太懂,继续总结吧。