什么是作用域
作用域: 用来描述变量或函数有限的访问范围。
通俗一点就是某个变量哪里可访问哪里不可访问。
作用域分类:
- 全局作用域: 非函数内的区域就是全局作用域(一般都是最外围)。
- 局部(函数)作用域: 在函数内部就是局部作用域
全局作用域(Global Scope)
- 在全局作用域定义的变量或函数我们称之为是全局变量或全局函数,在任何地方都可以被访问。
局部作用域(Local Scope)
- 在函数内部就是局部(函数)作用域
- 每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。
- 函数内部声明的变量我们称之为局部变量,它只能在所定义的函数内部可访问,在全局中是无法访问的。
代码示例:
<script>
// 全局作用域
var age = 18; // 全局变量
var email = '123@qq.com' // 全局变量
console.log(age); // 18
function getAge(){
// 局部作用域
var age = 20; // 局部变量
console.log('我今年 '+ age + '岁',', 邮箱是:',email)
}
getAge();
</script>
作用域链(scope chain)
作用域链:就是变量的查找规则(就近原则)。
要注意作用域和作用域链的区别
- 作用域描述的是变量作用的范围。
- 而作用域链决定了变量的查找规则(就近原则,由内往外层找)。
示例代码:
var age = '100';
function foo(){
console.log(age); // ?
function bar(){
var age = '18';
console.log(age); // ?
function par(){
console.log(age); // ?
}
par();
}
bar();
}
foo();
说明:
- 只有函数才可以形成作用域结构。如果没有函数那就只存在一个域,就是window全局作用域。
- 作用域链中查找变量的原则:就近原则。
就近原则:访问变量,会优先在自身的作用域查找,若没有找到,会继续往上一级作用域中去找,直到找到全局作用域,若找到就使用,找不到就报错(引用错误,这个变量没有定义 xxxx not a defined)
函数形参就是局部变量
var a = 1
var b = 2
function sum(a,b) {
// 形参就是函数内部局部变量
// 如果要改形参的值,不需要在重复var声明
b = b + 1
console.log(a + b) // 8
}
sum(3, 4)
未使用var声明的变量
注意:在函数内若定义一个未使用var声明的变量,此变量执行时会覆盖全局内同名的变量,这种代码是不规范的,一定要避免,否则对我们的代码造成意想不到的效果,也不利于代码调试。
示例:
var myname = "大锤";
function foo() {
myname = "小锤" // 要避免
}
foo();
console.log(myname) // 小·锤
注意:全局中默认有个特殊的变量name,值为空
''
,平时的测试代码不要被name的默认值给影响到。
JS预解析
JavaScript代码的执行是由浏览器中的JavaScript引擎来执行的。执行时,分为两个阶段:预解析
和执行
。
- 预解析阶段的工作:
主要做提升
- 把var变量的声明提升到当前作用域的最顶端。
- 把函数的声明提升到当前作用域的最顶端。
- 执行阶段的工作:
- 代码执行,如进行变量的赋值操作,和一些运算操作。
变量的提升
示例1:
console.log(a); // undefined
var a = 10;
由于var变量的提升,等价于:
var a; // 提升全局作用域顶端
console.log(a); // undefined
a = 10;
示例2:
var age = 18;
function foo(){
console.log(age); // undefined
var age = 19;
console.log(age); // 19
}
foo();
由于var变量会提升,等价于:
var age = 18
function foo(){
var age; // 提升到函数作用域顶端
console.log(age); // undefined
age = 19;
console.log(age); // 19
}
foo();
函数声明的提升
示例:
foo(); // 1
function foo(){
console.log(1);
}
由于函数声明会提升,等价于:
function foo(){ // 提升到顶端
console.log(1);
}
foo(); // 1
注意:同名情况下,函数声明提升优先级要高于同名的变量声明。
开发中是不会遇见的,但面试会!你懂的!
var foo;
function foo(){
console.log(2)
}
foo() // 2
由于提升:等价于
// 先提升
function foo(){
console.log(2)
}
// 后提升
var foo;
foo() // 2 优先会访问函数声明,函数是一等公民,
相关面试题
- 题1:
var a = 100;
function test(){
console.log(a); // ?
var a = 200;
}
test();
- 题2:
var a = 100;
function test(){
console.log(a); // ?
if(false){
var a = 200;
}
}
test();
一句话总结:任何代码块内,只要使用了var声明的变量都会被提升到当前作用域最顶端。
建议后面不管是变量还是函数,养成先定义后使用的好习惯。
匿名函数
匿名函数:即没有名字的函数
匿名函数如何使用:将匿名函数赋值给一个变量,这样就可以通过变量进行调用
function func(){} // 函数声明
// func就是匿名函数的名字 func本质就是指向函数所在堆中的内存地址
var func = function(){} // 函数表达式
func(); // 调用函数
自执行函数(IIFE)
IIFE,其全称为 immediately invoked function expression,即立即调用的函数表达式
IIFE作用:保存变量,形成一个私有空间(局部),不会污染全局变量。
应用场景:一般多用在封装插件中。 如Vue,React等框架都是IIFE形式。
自调用函数常用有两种定义方式:
;(function ([形参列表]) {
console.log('哈哈');
})([实参列表])
// 等价
;(function ([形参列表]) {
console.log('哈哈');
}([实参列表]))
var result = (function (num1, num2) {
return num1 + num2
})(30, 50)
console.log(result); // 80
注意:括号前面要求加一个分号;防止代码出错。
练习题:
;(function(){
console.log(foo); // undefined
var foo = 2;
console.log(foo); // 2
})();
var x = 5;
;(function () {
console.log(x); // undefined
var x = 10;
console.log(x); // 10
})();
var a = b = 10; // var a = 10; b = 10
; (function () {
// 连续赋值
var a = b = 20 // var a = 20; b = 20 相当于覆盖全局变量b,改为20
})();
console.log(a) // 10
console.log(b) // 20