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; //undefinedvar 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(); // ReferenceErrorlet 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();
