JS作用域:
在JavaScript中,作用域指的是程序中变量、函数和对象可访问的上下文环境。JavaScript主要有三种作用域:全局作用域、函数作用域和块作用域(ES6引入)。
- 全局作用域:全局作用域中的变量在页面上的所有脚本和函数中都可以访问。这些变量在JavaScript代码的任何地方都可以被访问和修改。全局作用域通常由var关键字在函数外部声明的变量、函数以及window对象的属性构成。
javascript">var globalVar = "I am global";
function globalFunction() {
console.log(globalVar); // 可以访问全局变量
}
globalFunction(); // 输出: I am global
- 函数作用域: 函数作用域是指在函数内部声明的变量只能在该函数内部访问,这些变量在函数外部是不可访问的。函数作用域由函数体创建,每次调用函数时都会创建一个新的作用域。
javascript">function myFunction() {
var functionVar = "I am local to the function";
console.log(functionVar); // 可以访问函数内部变量
}
myFunction();
console.log(functionVar); // Uncaught ReferenceError: functionVar is not defined
- 块作用域(ES6引入):块作用域是由{}包围的代码块(如if语句、for循环等)创建的作用域。在块作用域中声明的变量只能在该块内部访问。块作用域由
let
和const
关键字引入。
javascript">if (true) {
let blockVar = "I am local to the block";
console.log(blockVar); // 可以访问块内部变量
}
console.log(blockVar); // Uncaught ReferenceError: blockVar is not defined
注意事项:
- var声明的变量没有块作用域,只有函数作用域或全局作用域。
- let和const声明的变量具有块作用域。
- 在函数内部声明变量时,应优先考虑使用let或const而不是var,以避免潜在的作用域问题。
- 全局变量在整个JavaScript代码中都可以访问和修改,这可能导致命名冲突和意外的行为。因此,应谨慎使用全局变量。
函数调用及this指向:
javascript">function fun(){
console.log(123);
return 'abc';
console.log( fun ); //打印的是函数体
}
返回的是:
f fun(){
console.log(123);
return ‘abc’;
} 且函数体内的代码是不执行的。
javascript">function fun(){
this.xxx = '123456'
console.log(this);
return 'abc';
}
console.log( fun() ); // 调用函数执行
返回的是:window abc 且函数体内的代码都是执行的。此时的this打印的是window。普通函数的this是指向window的,因为执行是在全局进行执行的
javascript">function fun() {
this.xxx = '123456'
console.log(this);
return 'abc';
}
console.log(new fun());
当你使用new 关键字调用一个函数时,这个函数会被当做构造函数来处理并且会创建一个新的对象实例。而这个this关键字会指向这个新创建的对象实例。
- new fun()会创建一个新对象,并调用fun函数,其中this指向这个新对象。
- 在fun函数内部,console.log(this);会打印出这个新对象,包含xxx属性。
- 然后,new fun()会返回这个新对象实例。
- 最后,外层的console.log会再次打印这个新对象实例。
高频面试题例题一:
javascript"> function Foo() {
getName = function () {
console.log(1);
} //注意是全局的 window.getName
return this; //所以这里的this是window
}
Foo.getName = function () {
console.log(2);
}
Foo.prototype.getName = function () {
console.log(3);
}
var getName = function () {
console.log(4);
}
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo().getName();
输出:
javascript">Foo.getName(); // 2 由于这里不是Foo().getName 所以我们找的是Foo.getName
getName(); // 4 普通变量要大于函数,所以永远不可能打印出5
Foo().getName(); // 1 运行Foo()函数,后者覆盖前者,1覆盖了4 所以打印1
getName(); // 1
new Foo().getName(); // 3
// 对象查找一个属性或查找一个方法:
// 1.先本身找 2.去他的构造函数找 3.去他对象的原型中找 4.去构造函数的原型上找 5.去原型链上找
代码解读与优先级分析:
函数声明与变量声明:
- 在JavaScript中,函数声明会被提升到它们所在作用域的顶部,并且优先于变量声明。但是,如果同一个作用域内既有函数声明又有变量声明,并且它们同名,那么变量声明会覆盖函数声明。
- 在代码中,有两个全局的getName声明:一个函数声明(输出5那个)和一个变量声明(输出4那个)。由于变量声明(即使它包含一个函数)会覆盖函数声明,所以全局的getName最终指向的是变量声明中的函数(输出4的那个)。但是,这个全局的getName在Foo函数内部被重新赋值了(输出1的那个),所以这个覆盖关系在Foo函数执行后发生了变化。
Foo函数内部的getName:
在Foo函数内部,getName被重新赋值为一个新的函数(输出1的那个),并且这个新函数被显式地设置为window对象的属性。这意味着,一旦Foo函数被执行,全局的getName就会被改变。
Foo的静态方法与原型方法:
- Foo.getName是Foo函数对象的一个静态方法,它与Foo的实例无关。
- Foo的原型方法:Foo.prototype.getName是Foo实例的原型上的一个方法。当你通过new Foo()创建一个Foo的实例并调用其getName方法时,调用的就是这个原型上的方法。
调用顺序与输出:
- Foo.getName();:调用的是Foo的静态方法,输出2。
- getName();:在Foo函数执行之前,调用的是全局变量getName所指向的函数,输出4。(变量声明会覆盖函数声明)
- Foo().getName();:执行Foo函数,该函数改变了全局的getName,然后调用改变后的全局getName,输出1。
- getName();:由于全局的getName已经被Foo函数改变,所以再次调用时仍然输出1。
- new Foo().getName();:创建一个Foo的实例,并调用其原型上的getName方法,输出3。
总结:
- 全局作用域中的函数声明会被变量声明覆盖(如果它们同名)。
- 在函数内部对全局变量的修改会影响全局作用域。
- 静态方法属于函数对象本身,与实例无关。
- 实例方法位于函数的原型上,通过实例来调用。
- 当通过 new 关键字创建的实例调用一个方法时,查找顺序是:实例本身 -> 实例的原型 -> 构造函数的原型链 -> null
- 原型链的查找顺序是:实例本身 -> 构造函数 -> 原型对象 -> 构造函数的原型(如果有的话)-> … -> null。
例题二:
javascript"> var o = {
a: 10,
b: {
fn: function () {
console.log(this.a);
console.log(this);
}
}
}
o.b.fn();
此时fn是o.b调用的,所以这里的this是o.b这个对象。o.b本身是没有a的那么这里的this.a就是undefined。
javascript"> var o = {
a: 10,
b: {
a:2,
fn: function () {
console.log(this.a);
console.log(this);
}
}
}
o.b.fn();
此时fn是b调用的,所以这里的this是o.b这个对象。o.b本身是没有a的那么这里的this.a就是2。
javascript"> var o = {
a: 10,
b: {
a:2,
fn: ()=> {
console.log(this.a);
console.log(this);
}
}
}
o.b.fn();
fn 是一个箭头函数,它是在对象o.b的上下文中定义的,但实际上这个上下文对于this的值并没有影响,因为箭头函数不会从它的封闭执行上下文(在这里是o.b)继承this。相反,它会捕获定义它时的外部函数的this值,或者如果它是在全局上下文中定义的,则捕获全局对象。在浏览器中通常是window
在全局并没有定义a这个变量,所以输出this.a是undefined。
例题三:
javascript">window.name = 'Kitty';
function A(){
this.name = 123;
}
A.prototype.getA = function(){
console.log(this);
console.log(this.name + 1);
return this.name + 1 ;
}
let a = new A();
let funcA = a.getA;
funcA();
- 使用new关键字调用了构造函数A,创建了一个新对象a。此时,a的name属性被设置为123。
- 从对象a上获取了getA方法的引用,并将其存储在变量funcA中。重要的是要注意,此时funcA只是一个函数引用(函数体),它不再与a对象绑定。
- 调用了funcA函数。由于funcA是在全局作用域中作为普通函数调用的(而不是作为a的方法调用),因此this在funcA函数内部指向全局对象(在浏览器中通常是window)。
- 所以这里的 let funcA = a.getA; 为:
javascript">let funcA = function(){
console.log(this);
console.log(this.name + 1);
return this.name + 1 ;
}
- console.log(this.name + 1);会尝试访问全局对象的name属性(之前设置为’Kitty’),然后将其与1相加。字符串拼接 为
'Kitty1
。
javascript">window.name = 'Kitty';
function A(){
this.name = 123;
}
A.prototype.getA = function(){
console.log(this);
console.log(this.name + 1);
return this.name + 1 ;
}
let a = new A();
let funcA = a.getA();
funcA;
如果是上述这样,那么a.getA()就是函数方法的调用,即这里的a的name是 123 ,这里的this代表的是A:
javascript">function A(){
this.name = 123;
}
那么console.log(this.name + 1);就是123+1 即124
例题四:
javascript">var length = 10;
function fn(){
return this.length + 1;
}
var obj = {
length: 5,
test1: function(){
return fn();
}
}
obj.test2 = fn;
console.log(obj.test1()); // 11
console.log(fn() === obj.test2()); // false
console.log(obj.test1() == obj.test2()); // false
解析 obj.test2 = fn
; fn没有加括号,相当于赋值函数体:
javascript">obj.test2 = function (){
return this.length + 1;
};
这里obj.test2 相当于在obj对象里有一个test2,即:
javascript">var obj = {
length: 5,
test1: function(){
return fn();
}
test2: function(){
return this.length + 1;
}
}
console.log(obj.test1()):
test1里面的return fn(); fn是加括号的,相当于运行的是:
javascript">function fn(){
return this.length + 1;
}
这里fn是全局的函数,this是winow,全局的length是10,所以这里的console.log(obj.test1()); // 输出:11 即10+1=11
console.log(fn() === obj.test2()); :
fn是一个普通函数,this指向window,那么fn()的结果是11。
javascript">var obj = {
length: 5,
test1: function(){
return fn();
}
test2: function(){
return this.length + 1;
}
}
obj.test2()的this指向obj,length是5,即结果是5+1=6.
11 不等于6 返回false。
console.log(obj.test1() == obj.test2());:
obj.test2()综上所属是6.
obj.test1()是一个闭包返回 11
11 不等于 6 返回false
箭头函数和普通函数的this指向:
普通函数的this指向调用该函数的对象:
- 独立调用时,指向全局对象(window或global)
- 被对象调用时,指向调用它的对象
- 使用call、apply或bind方法调用时,指向他们的第一个参数
- 在构造函数中使用时,指向实例化该构造函数的对象
箭头函数没有自己的this:
- 在全局作用域定义时,this指向window,若this.xxx没有则为undefined。
- 在普通函数中定义时,this继承普通函数的this指向,即继承父级作用域的this。