7.1全局变量和局部变量
变量的生命周期 声明 创建 开辟空间 销毁的过程
作用域决定了变量、常量和参数被定义的时间和位置。
变量的作用域决定了变量的生命周期
作用域具有层次结构,它有一个顶端(最大作用域):任何程序只要开始运行,就隐式地运行在某个作用域中,这就是全局作用域。当一个JavaScript程序开始运行时,在任何函数被调用之前,它都运行在全局作用域内。
在全局作用域中声明的变量叫全局变量。
在块作用域或方法中声明的变量叫局部变量
全局变量和局部变量的区别
作用域不同:全局变量作用于全局。局部变量作用于局部。
生命周期不同:全局变量保存在静态存贮区,程序结束释放该内存。局部变量是动态分配、动态释放。
不同作用域中允许存在相同名字的变量和常量
let name = "rxy"; //全局变量
console.log(name);
{
const x = 'blue'; //局部变量
console.log(x);
console.log(name);
}
console.log(typeof x);
{
const x = 'red';
console.log(x);
console.log(name);
}
函数参数的作用域仅限于函数体中。出了函数体参数就不复存在了。
当某些变量(参数)不复存在时,JavaScript不一定会立即回收内存:它只是标记这些条目不再需要保留,垃圾回收进程会定期去回收。在JavaScript中,不需要关心垃场回收,因为这个过程是自动化的。
function f(x){
return x+3;
}
console.log(f(5));
console.log(x);
变量必须在调用之前声明
可以将超市里的水果看成是数据,比如香蕉2个,苹果1个等。决定做水果捞,需要买一个搅拌机。
搅拌机更像一个函数:它能做一些事情(比如将水果搅成水果捞)。搅拌机,没有通电,此时它仅仅是静态物品。相当于函数的声明。通电开始搅拌,相当于函数调用。调用时水果必须准备好,放到搅拌机当中。所以函数中的变量必须在调用之前声明。
let banana = 2; //香蕉
let apple = 1; //苹果
function stir() { //方法声明
console.log(banana);
console.log(apple);
}
stir();
7.2减少全局变量的声明
let name = "Irena"; //全局变量
let age = 25; //全局变量
function greet() {
console.log(`Hello,${name}!`);
}
function getBirthYear() {
return new Date().getFullYear() - age;
}
//将用户信息封装到一个单独对象中
let user = {
name:"Irean",
aget:25
}
function greet() {
console.log(`Hello,${user.name}!`);
}
function getBirthYear() {
return new Date().getFullYear() - user.age;
}
//优化函数的参数
function greet(user) {
console.log(`Hello,${user.name}!`);
}
function getBirthYear(user) {
return new Date().getFullYear() - user.age;
}
7.3变量屏蔽
作用域嵌套需要注意变量屏蔽
{
let x = 'blue';
console.log(x); //打印blue
{
let x = 3;
console.log(x); //打印3
}
console.log(x); //打印blue
}
console.log(typeof x); //打印undefined
7.4函数闭包
在ES6之前,可能会把所有函数都定义在全局作用域内,并在函数中避免去访问全局变量,不用考虑函数作用域。
在现在JavaScript开发中,通常会把函数定义在块用域中。将它们赋给变量和对象属性、或数组中。
将函数定义在一个块作用域中,并明确的指出它对该作用域所具备访问权限,通常称这种形式为闭包(封闭函数的作用域)
//封装思想 封装的思想 变量(属性)不可以随便访问,需要通过方法对变量进行访问控制
function getNameFun(){
let name = "rxy"; //局部变量 访问权限小
function getName(){ //提供了一公共的方法,访问变量
console.log(name);
}
//两种返回方式
//return getName();//return 函数调用
return getName; //return 函数引用
}
//两种调用方式
console.log(getNameFun());
getNameFun()();
//闭包的调用
let getNameGlobal = getNameFun();
getNameGlobal();
//函数的闭包 也有封装的思想
// 1 局部变量 有限的作用域
// 2 函数要访问使用局部变量
// 3 内部函数引用赋值给外部变量(固定内部函数的地址)
//JavaScript学习指南书上的闭包案例
let globalFunc; // 谁都能访问 会一直存在内存当中
{
let blockVar = "a";//局部变量
globalFunc = function(){ //返回方法,提供给外部使用
console.log(blockVar);//方法要访问使用局部变量
}
}
globalFunc();
//书上的案例
let f;
{
let o={
note:"Safe",
setNote(note){
this.note = note;
}
};
f= function(){
return o;
}
}
let oRef = f();
console.log(oRef.note);
oRef.setNote("这是安全的");
// oRef.note = "Not so safe after all!";
// console.log(oRef.note);
7.5即时调用
声明了一个函数,并立即执行该函数,就叫做即时调用IIFE
(function() {
// 这里的代码立即执行
})();
在ES6中,虽然块作用域变量从某种程度上减少了对IIFE的需求,但IIFE仍然很常用。通常和闭包一起使用。
(function(){
console.log("立即执行IIFE");
})();
let count = 0; //全局变量
function addGlobal(){
count++;
console.log("函数被调用了1111:"+count);
}
addGlobal();
addGlobal();
addGlobal();
function c(){
count =20;
}
c();
addGlobal(); //全局变量的弊端
function addTemp(){
let count = 0;
count++;
console.log("函数被调用了2222:"+count);
}
addTemp();
addTemp();
addTemp();
function addCount(){
let count = 0;
function add(){
count++;
console.log("函数被调用了3333:"+count);
}
return add;
}
// addCount() 调用了三次 let count = 0; 初始化了三次 缺点
addCount()();
addCount()();
addCount()();
//addCount() 调用了一次 let count = 0; 初始化了一次
let addCountNew = addCount();
addCountNew();
addCountNew();//所以再次调用实现了累加
addCountNew();
let globalFunc; // 全局变量 缺点
{
let count = 0;
globalFunc = function(){
count++;
console.log("函数被调用了4444:"+count);
}
}
globalFunc();
globalFunc();
globalFunc();
const f= (function(){
let count = 0;
return function(){
count++;
console.log("函数被调用了5555:"+count);
}
})();
f();
f();
f();
7.6变量函数的作用域提升
在ES6引入let 关键字之前, 变量都是用var来声明的, 并且存在函数作用域的说法。
使用let声明变量, 变量只在声明之后才存在。 而使用var声明, 变量在当前作用域中任意地方都可用, 甚至可以在声明前使用。
x; //ReferenceError:
let x= 3; // 永远不会执行,因为错误终止了程序
console.log(x);
var声明的变量采用了提升机制。
var声明的变量,可以在声明前被引用。
x; //undefined
var x = 3;
console.log(x); //3
// 代码有点难以理解, 因为不可能去访问一个还未声明的变量。
// 实际上, var声明的变量采用了提升机制。
// JavaScript 会扫描整个作用域( 函数或者全局作用域),
// 任何使用var声明的变量都会被提升至作用域的顶部(被提升的是声明, 不是赋值)。
// 所以JavaScript 会将之前例子中的代码翻译成下面的形式:
var x;
x;
x = 3;
console.log(x);
使用var声明的变量,JavaScript不会关心它是否有重复声明。
因为有很多遗留代码使用了var,ES6不能简单地修复var,因此,它引入了新的关键字let。
let最终会完全取代var
类似于var声明的变量,函数声明也会被提升至它们作用域的顶部,这允许在函数声明之前调用。
f();
function f() {
console.log("f");
}
赋给变量的函数表达式不会被提升,它们的作用域规则跟变量是一样的
f(); // ReferenceError
let f = function() {
console.log("f");
}
7.7临时死区
在JavaScript 中,使用let声明的变量只有在被显式声明之后才存在,这就是临时死区(TDZ)
也就是说,在给定的作用域内,变量的TDZ是指该变量被声明之前的代码。
在let命令声明变量x之前,都属于变量x的临时死区(TDZ)。
typeof的“死区”陷阱
typeof 可以用来检测给定变量的数据类型,也可以使用它来判断值是否被定义。当返回undefined时表示值未定义;但是在const/let定义的变量在变量声明之前如果使用了typeof就会报错
if (typeof x === "undefined") {
console.log("x 不存在");
} else {
console.log("定义x")
}
let x = 5;
在 ES6中,没有必要使用typeof来检查变量是否被定义。所以,实际使用的时候,typeof在TDZ中的行为不应该引发任何问题。
7.8严格模式
ES5的语法允许存在隐式全局变量,而这正是那些令人懊恼的编程错误的源头。
简而言之,如果忘记使用var声明某个变量,JavaScript 会不假思索地认为开发人员在引用一个全局变量。
如果该全局变量不存在,它会替开发人员创建一个!出于这个原因,JavaScript引进了严格模式,它能阻止隐式全局变量。
在开始编写代码之前,单独插入一行字符串”use strict”(单引号和双引号都可以)就可以启用严格模式。
如果在全局作用域中这么做,整个脚本会以严格模式执行。而如果在函数中使用它,该函数就会以严格模式执行。
严格模式下this 是undefined
非严格模式下this 是Window
"use strict"
var name = "rxy";
function f(){
console.log(this);
}
f();
function f1(){
console.log("姓名:"+this.name);
}
f1();