7.1全局变量和局部变量

变量的生命周期 声明 创建 开辟空间 销毁的过程
作用域决定了变量、常量和参数被定义的时间和位置。
变量的作用域决定了变量的生命周期
作用域具有层次结构,它有一个顶端(最大作用域):任何程序只要开始运行,就隐式地运行在某个作用域中,这就是全局作用域。当一个JavaScript程序开始运行时,在任何函数被调用之前,它都运行在全局作用域内。
在全局作用域中声明的变量叫全局变量。
在块作用域或方法中声明的变量叫局部变量
全局变量和局部变量的区别
作用域不同:全局变量作用于全局。局部变量作用于局部。
生命周期不同:全局变量保存在静态存贮区,程序结束释放该内存。局部变量是动态分配、动态释放。
不同作用域中允许存在相同名字的变量和常量

  1. let name = "rxy"; //全局变量
  2. console.log(name);
  3. {
  4. const x = 'blue'; //局部变量
  5. console.log(x);
  6. console.log(name);
  7. }
  8. console.log(typeof x);
  9. {
  10. const x = 'red';
  11. console.log(x);
  12. console.log(name);
  13. }

函数参数的作用域仅限于函数体中。出了函数体参数就不复存在了。
当某些变量(参数)不复存在时,JavaScript不一定会立即回收内存:它只是标记这些条目不再需要保留,垃圾回收进程会定期去回收。在JavaScript中,不需要关心垃场回收,因为这个过程是自动化的。

  1. function f(x){
  2. return x+3;
  3. }
  4. console.log(f(5));
  5. console.log(x);

变量必须在调用之前声明
可以将超市里的水果看成是数据,比如香蕉2个,苹果1个等。决定做水果捞,需要买一个搅拌机。
搅拌机更像一个函数:它能做一些事情(比如将水果搅成水果捞)。搅拌机,没有通电,此时它仅仅是静态物品。相当于函数的声明。通电开始搅拌,相当于函数调用。调用时水果必须准备好,放到搅拌机当中。所以函数中的变量必须在调用之前声明。

  1. let banana = 2; //香蕉
  2. let apple = 1; //苹果
  3. function stir() { //方法声明
  4. console.log(banana);
  5. console.log(apple);
  6. }
  7. stir();

7.2减少全局变量的声明

  1. let name = "Irena"; //全局变量
  2. let age = 25; //全局变量
  3. function greet() {
  4. console.log(`Hello,${name}!`);
  5. }
  6. function getBirthYear() {
  7. return new Date().getFullYear() - age;
  8. }
  9. //将用户信息封装到一个单独对象中
  10. let user = {
  11. name:"Irean",
  12. aget:25
  13. }
  14. function greet() {
  15. console.log(`Hello,${user.name}!`);
  16. }
  17. function getBirthYear() {
  18. return new Date().getFullYear() - user.age;
  19. }
  20. //优化函数的参数
  21. function greet(user) {
  22. console.log(`Hello,${user.name}!`);
  23. }
  24. function getBirthYear(user) {
  25. return new Date().getFullYear() - user.age;
  26. }

7.3变量屏蔽

作用域嵌套需要注意变量屏蔽

  1. {
  2. let x = 'blue';
  3. console.log(x); //打印blue
  4. {
  5. let x = 3;
  6. console.log(x); //打印3
  7. }
  8. console.log(x); //打印blue
  9. }
  10. console.log(typeof x); //打印undefined

7.4函数闭包

在ES6之前,可能会把所有函数都定义在全局作用域内,并在函数中避免去访问全局变量,不用考虑函数作用域。
在现在JavaScript开发中,通常会把函数定义在块用域中。将它们赋给变量和对象属性、或数组中。
将函数定义在一个块作用域中,并明确的指出它对该作用域所具备访问权限,通常称这种形式为闭包(封闭函数的作用域)

  1. //封装思想 封装的思想 变量(属性)不可以随便访问,需要通过方法对变量进行访问控制
  2. function getNameFun(){
  3. let name = "rxy"; //局部变量 访问权限小
  4. function getName(){ //提供了一公共的方法,访问变量
  5. console.log(name);
  6. }
  7. //两种返回方式
  8. //return getName();//return 函数调用
  9. return getName; //return 函数引用
  10. }
  11. //两种调用方式
  12. console.log(getNameFun());
  13. getNameFun()();
  14. //闭包的调用
  15. let getNameGlobal = getNameFun();
  16. getNameGlobal();
  17. //函数的闭包 也有封装的思想
  18. // 1 局部变量 有限的作用域
  19. // 2 函数要访问使用局部变量
  20. // 3 内部函数引用赋值给外部变量(固定内部函数的地址)
  21. //JavaScript学习指南书上的闭包案例
  22. let globalFunc; // 谁都能访问 会一直存在内存当中
  23. {
  24. let blockVar = "a";//局部变量
  25. globalFunc = function(){ //返回方法,提供给外部使用
  26. console.log(blockVar);//方法要访问使用局部变量
  27. }
  28. }
  29. globalFunc();
  30. //书上的案例
  31. let f;
  32. {
  33. let o={
  34. note:"Safe",
  35. setNote(note){
  36. this.note = note;
  37. }
  38. };
  39. f= function(){
  40. return o;
  41. }
  42. }
  43. let oRef = f();
  44. console.log(oRef.note);
  45. oRef.setNote("这是安全的");
  46. // oRef.note = "Not so safe after all!";
  47. // console.log(oRef.note);

7.5即时调用

声明了一个函数,并立即执行该函数,就叫做即时调用IIFE

  1. (function() {
  2. // 这里的代码立即执行
  3. })();

在ES6中,虽然块作用域变量从某种程度上减少了对IIFE的需求,但IIFE仍然很常用。通常和闭包一起使用。

  1. (function(){
  2. console.log("立即执行IIFE");
  3. })();
  4. let count = 0; //全局变量
  5. function addGlobal(){
  6. count++;
  7. console.log("函数被调用了1111:"+count);
  8. }
  9. addGlobal();
  10. addGlobal();
  11. addGlobal();
  12. function c(){
  13. count =20;
  14. }
  15. c();
  16. addGlobal(); //全局变量的弊端
  17. function addTemp(){
  18. let count = 0;
  19. count++;
  20. console.log("函数被调用了2222:"+count);
  21. }
  22. addTemp();
  23. addTemp();
  24. addTemp();
  25. function addCount(){
  26. let count = 0;
  27. function add(){
  28. count++;
  29. console.log("函数被调用了3333:"+count);
  30. }
  31. return add;
  32. }
  33. // addCount() 调用了三次 let count = 0; 初始化了三次 缺点
  34. addCount()();
  35. addCount()();
  36. addCount()();
  37. //addCount() 调用了一次 let count = 0; 初始化了一次
  38. let addCountNew = addCount();
  39. addCountNew();
  40. addCountNew();//所以再次调用实现了累加
  41. addCountNew();
  42. let globalFunc; // 全局变量 缺点
  43. {
  44. let count = 0;
  45. globalFunc = function(){
  46. count++;
  47. console.log("函数被调用了4444:"+count);
  48. }
  49. }
  50. globalFunc();
  51. globalFunc();
  52. globalFunc();
  53. const f= (function(){
  54. let count = 0;
  55. return function(){
  56. count++;
  57. console.log("函数被调用了5555:"+count);
  58. }
  59. })();
  60. f();
  61. f();
  62. f();

7.6变量函数的作用域提升

在ES6引入let 关键字之前, 变量都是用var来声明的, 并且存在函数作用域的说法。
使用let声明变量, 变量只在声明之后才存在。 而使用var声明, 变量在当前作用域中任意地方都可用, 甚至可以在声明前使用。

  1. x; //ReferenceError:
  2. let x= 3; // 永远不会执行,因为错误终止了程序
  3. console.log(x);

var声明的变量采用了提升机制。
var声明的变量,可以在声明前被引用。

  1. x; //undefined
  2. var x = 3;
  3. console.log(x); //3
  4. // 代码有点难以理解, 因为不可能去访问一个还未声明的变量。
  5. // 实际上, var声明的变量采用了提升机制。
  6. // JavaScript 会扫描整个作用域( 函数或者全局作用域),
  7. // 任何使用var声明的变量都会被提升至作用域的顶部(被提升的是声明, 不是赋值)。
  8. // 所以JavaScript 会将之前例子中的代码翻译成下面的形式:
  9. var x;
  10. x;
  11. x = 3;
  12. console.log(x);

使用var声明的变量,JavaScript不会关心它是否有重复声明。
image.png
因为有很多遗留代码使用了var,ES6不能简单地修复var,因此,它引入了新的关键字let。
let最终会完全取代var
类似于var声明的变量,函数声明也会被提升至它们作用域的顶部,这允许在函数声明之前调用。

  1. f();
  2. function f() {
  3. console.log("f");
  4. }

赋给变量的函数表达式不会被提升,它们的作用域规则跟变量是一样的

  1. f(); // ReferenceError
  2. let f = function() {
  3. console.log("f");
  4. }

7.7临时死区

在JavaScript 中,使用let声明的变量只有在被显式声明之后才存在,这就是临时死区(TDZ)
也就是说,在给定的作用域内,变量的TDZ是指该变量被声明之前的代码。
在let命令声明变量x之前,都属于变量x的临时死区(TDZ)。
typeof的“死区”陷阱
typeof 可以用来检测给定变量的数据类型,也可以使用它来判断值是否被定义。当返回undefined时表示值未定义;但是在const/let定义的变量在变量声明之前如果使用了typeof就会报错

  1. if (typeof x === "undefined") {
  2. console.log("x 不存在");
  3. } else {
  4. console.log("定义x")
  5. }
  6. let x = 5;

在 ES6中,没有必要使用typeof来检查变量是否被定义。所以,实际使用的时候,typeof在TDZ中的行为不应该引发任何问题。

7.8严格模式

ES5的语法允许存在隐式全局变量,而这正是那些令人懊恼的编程错误的源头。
简而言之,如果忘记使用var声明某个变量,JavaScript 会不假思索地认为开发人员在引用一个全局变量。
如果该全局变量不存在,它会替开发人员创建一个!出于这个原因,JavaScript引进了严格模式,它能阻止隐式全局变量。
在开始编写代码之前,单独插入一行字符串”use strict”(单引号和双引号都可以)就可以启用严格模式。
如果在全局作用域中这么做,整个脚本会以严格模式执行。而如果在函数中使用它,该函数就会以严格模式执行。
严格模式下this 是undefined
非严格模式下this 是Window

  1. "use strict"
  2. var name = "rxy";
  3. function f(){
  4. console.log(this);
  5. }
  6. f();
  7. function f1(){
  8. console.log("姓名:"+this.name);
  9. }
  10. f1();