JavaScript语言的三部分构成

按照相关的JS语法,去操作页面中的元素,有时还要操作浏览器里面的一些功能

1—ECMAScript(ES)3/5/6…:
JS的语法规范(变量,数据类型,操作语句等等),描述了该语言的语法和基本对象
2—DOM(document object model):
文档对象模型(描述处理网页内容的方法和接口),提供一些JS的属性和方法,用来操作页面中的Dom元素
3—BOM(browser object model):
浏览器对象模型(描述与浏览器进行交互的方法与接口),提供一些JS的属性和方法,用来操作浏览器的

JavaScript中的变量 variable

变量:可变的量。在编程语言中,变量就是一块内存空间,用来存储和代表不同值的东西。

  • 是用来存储值的一块内存空间
  • 若存储的是基本数据类型数据,则值数据本身就存储在该变量的内存空间中 【存的是值】
  • 若要存储的值为引用数据类型,则该变量中存储的是值的【堆内存地址】 【存的是值的地址】

创建变量的几种方式

var / let / const / function / class / import / Symbol

  1. //基于var创建变量n,让其指向具体的值10
  2. var n = 10;
  3. //创建变量m,但不未赋值,默认指向undefined
  4. var m;
  5. console.log(n,m);//=> 输出10 undefined
  6. let a = 100;//基于let创建一个叫做a的变量,值100
  7. a = 200; //修改值
  8. console.log(a);//=>输出200
  9. const b = 1000; //基于const创建变量b,值1000
  10. b = 200;
  11. console.log(b); //报错:Uncaught TypeError: Assignment to constant variable.指向不许被修改
  12. //创建一个函数:也可以理解为创建一个变量func,让其指向这个函数
  13. function func(){}
  14. console.log(func);//=> 输出func函数本身 func(){}
  15. //创建一个类:也可以理解为创建一个变量Parent,让其指向这个类
  16. class Parent{}
  17. console.log(Parent);//=>输出 class Parent{}
  18. //基于模块规范来导入具体的某个模块:定义一个叫做axios的变量,用来指向导入的这个模块
  19. import axios from './axios';//相当于let axios = require('./axios');
  20. let c = Symbol (1000) //创建一个唯一值

各变量的特点和区别

  • ES3/5中创建变量用 var 通过var定义的全局变量和函数都会成为window对象的属性和方法
  • ES6中创建变量用 let、const(与var的区别在于变量提升)通过let、const创建的顶级声明不会定义在全局上下文中 但作用域链解析效果是一样
  • let创建的是 变量 ,只不过他的指针指向可以随意的修改。
  • const创建的是 看上去像常量的变量 ,只不过他的 【指针指向一旦确定,就不能再修改】所以const创建的变量值是不允许被修改的**【看起来像但并不是常量】**
    const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个引用类型(对象或数组)声明为常量可能会被修改

    1. const a = { name: "gao"}
    2. let b = a;
    3. b.name = "zhang"
    4. console.log(b); //name: "zhang"
    5. console.log(a); //name: "zhang" a被修改
    6. // const a = [1, 2, 3]
    7. // let b = a;
    8. // b[0] = 3; 数组也一样
  • Function 函数【堆内存】function创建函数

    • 函数的三种角色【会在函数章节再来细谈】
      1. 普通函数(作用域和作用域链)
      2. 构造函数(类/实例/原型和原型链)
        • (在函数在作为构造函数执行时,会创建一个实例,此时的函数自身相当于一个类)
      3. 普通对象(键值对/属性值属性名)
  • Class 类【堆内存】 class创建类(ES6中创建自定义类的新语法)
  • 导入模块 import导入模块【也可以创建变量】
  • Symbol (1000) 创建一个唯一值

变量提升

当前作用域中JS 代码自上而下执行之前 浏览器会吧所有带有var / function关键字的进行提前声明或定义

var 关键字只是提前声明了一下 带function 关键字的在变量提升阶段吧声明和定义都完成了

  1. //变量提升 => var x; var y; fn = xx001(堆地址)
  2. console.log(x); //undefied
  3. var x = 3, y = 4;
  4. console.log(x);//3
  5. console.log(fn);// ƒ fn() {}
  6. function fn() {}

定义变量时 带var 和不带var区别?

  1. console.log(a); //undefied
  2. var a = 12;
  3. console.log(a); //12
  4. console.log(window.a); //12
  1. console.log(a); //undefied
  2. a = 12;
  3. console.log(a); //12
  4. console.log(window.a); //12

带var:在当前作用域中声明一个变量 如果是全局作用域 也就相当于给全局作用域设置一个属性叫a 也就是在window上添加了一个属性a

不加var : a 只是Window的一个属性 (把window省略了) 不是严格意义的变量

只对等号左边的进行变量提升

  • = 赋值 左边是变量 右边永远是值
  • = 右边也可以是三元表达式 a = b?c:d 但也是先运算出结果在赋值
  • = 右边也可能是函数 这样的叫:匿名函数:函数表示式(把函数当做一个值)
  1. console.log(fn) //undefined
  2. var fn = function(){}
  3. console.log(fn) //函数本身
  1. sum(); //TypeError: sum is not a function
  2. var sum = function(){}
  3. // =>上面代码相当于直接输出sum 和console一样 但此时sum还没定义 所以报错
  4. fn(); // aaa
  5. function fn(){console.log("aaa");}
  6. fn(); // aaa
  7. //这种写法 fn已经提升并定义 所以 fu()在上或在下都可以运行

在真实项目中 应用这个原理 我们创建函数时可以用函数表达式 这样更严谨 因为只能对等号左边的进行提升,所以变量提升完成后 当前函数只是声明了,没有定义 想要执行函数只能放在赋值的代码之后执行,这样让我们代码更有逻辑

不管条件是否成立都要进行变量提升

  1. var aa = 1;
  2. function bb(){
  3. //变量提升: 私有变量: var aa ,var b
  4. aa = 33;
  5. console.log(aa); //33 =>这里是函数作用域下第一次修改aa的值
  6. if(1 === 1){
  7. var b = 4;
  8. var aa = 44
  9. // aa = 44 如果不声明var 这里将修改全局都aa
  10. }
  11. console.log(aa,b);//44 4 =>第二次修改aa的值 b是4
  12. }
  13. bb()
  14. console.log(aa); //1 全局 如果上面不声明var 这里值将为44

关于重名的处理


在变量提升阶段** 如果名字重复了 不会重新进行声明,但是会重新进行定义(后面的赋值会把前面的赋值给替换掉)

  1. // 变量提升 : fn = (0x001)=(0x002)=(0x003)=(0x004)最后是4
  2. fn() // => 4
  3. function fn(){ console.log(1)} //(0x001)
  4. fn() // => 4
  5. function fn(){ console.log(2)} //(0x002)
  6. fn() // => 4
  7. var fn = 13 // => fn=13
  8. fn() //这里将变成 : 13() 所以会报错 fn is not a function 停止运行
  9. function fn(){ console.log(3)} //(0x003)
  10. fn()
  11. function fn(){ console.log(4)} //(0x004)
  12. fn()

函数优先

每遇到一个 var 关键字的变量声明,首先会查询当前作用域之前是否已经有了该名称的变量,如果是,则会忽略该声明;如果没有则把该变量声明提升
变量声明和函数声明都会被提升,那么在重复声明的情况下
预解析时函数首先被提升,然后才到变量。

  1. fn(); // 1
  2. var fn;
  3. function fn() { console.log(1); }
  4. var fn = function() { console.log(2); }
  5. fn(); // 2

虽然 var fn; 出现在 function fn (){…} 之前,但因为首先提升函数,而同名的 var 声明就被忽略了。尽管同名的 var 声明会被忽略掉,但是后出现的函数声明是能够覆盖前面的。

升只会提升函数声明,而不会提升函数表达式。

  1. console.log(foo1); // [Function: foo1]
  2. foo1(); // foo1
  3. console.log(foo2); // undefined
  4. foo2(); // TypeError: foo2 is not a function
  5. function foo1 () {
  6. console.log("foo1");
  7. };
  8. var foo2 = function () {
  9. console.log("foo2");
  10. }
  11. // 这里可能会有人有疑问? 为foo2会报错,不同样也是声明?
  12. // foo2在这里是一个函数表达式且不会被提升

一个函数提升案例

  1. var a = 1;
  2. function foo() {
  3. a = 10;
  4. console.log(a);
  5. return;
  6. function a() {};
  7. }
  8. foo();
  9. console.log(a);
  1. function a () {}等同于 var a = function() {};
  1. var a = 1; // 定义一个全局变量 a
  2. function foo() {
  3. // 首先提升函数声明function a () {}到函数作用域顶端
  4. // 然后function a () {}等同于 var a = function() {};最终形式如下
  5. var a = function () {}; // 定义局部变量 a 并赋值。
  6. a = 10; // 修改局部变量 a 的值,并不会影响全局变量 a
  7. console.log(a); // 打印局部变量 a 的值:10
  8. return;
  9. }
  10. foo();
  11. console.log(a); // 打印全局变量 a 的值:1

暂时性死区

ES6 明确规定,代码块({})中如果出现 let 和 const 声明的变量,这些变量的作用域会被限制在代码块内,也就是块级作用域

  1. console.log(a); //undefined
  2. var a = 1;
  3. console.log(b); //报错 Cannot access 'b' before initialization
  4. let b = 1;
  1. var a = 1;
  2. if(true){
  3. a = 2;
  4. console.log(a); //??
  5. let a; 如果是var 结果又是多少
  6. }
  7. console.log(a); //??
  1. var a = 1;
  2. if(true){ //这里加if 是为了一个块级作用域 也为了可以读到全局都a
  3. // 死区开始--------------------------
  4. // 访问 a 都会报错,不能在声明之前使用
  5. a = 2;
  6. console.log(a);
  7. // 死区结束-------------------------- 这里之前都会报错代码也不会往下执行 let\const必须先声明在使用
  8. // 以下是常规写法
  9. let a; //如果这里是 var 则会变量提升到全局 修改全局都a
  10. console.log(a); // undefined
  11. a = 3;
  12. console.log(a); // 3
  13. }
  14. console.log(a); //1 如果是var a的值被修改成3

let 和 const 声明变量之前不可以使用这些变量,这就是所谓的 暂时性死区。
总之:let \ const必须先声明在使用

初步了解执行上下文、作用域链

全局上下文是最外层的上下文,也就是我们常说的window ,通过var定义的全局变量和函数都会成为window对象的属性和方法 ,通过let、const创建的顶级声明不会定义在全局上下文中 但作用域链解析效果是一样。

上下文在其代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出才会销毁,例如关闭网页退出浏览器)

作用域链

函数执行形成一个私有作用域(保护私有变量)进入到私有作用域中 首先变量提升(声明过的变量都是私有的) 接下来执行代码

  1. 执行时遇到变量 如果这个变量是私有 那么按照私有处理即可
  2. 如果当这个变量不是私有 就向上级查询 一直查到window全局作用域为止 这种机制叫作用域链
  • 如果上级作用域没有这个变量(找到window也没有):
    变量= 值 相当于给window设置了这个属性 以后再操作window下就有了
    如果直接输出(alert(变量))此时没有就会报错
  1. console.log(x, y); // undefined x 2
  2. var x = 10,
  3. y = 20; // x = 10;y=20
  4. function fn() {
  5. //开辟堆内存 形成一个私有作用域
  6. // 变量提升 var x (私有变量)
  7. console.log(x, y); // x为undefined y是20【全局】
  8. var x = y = 100; // x =100 (私有) y = 100 【全局】
  9. console.log(x, y); // 100 100
  10. }
  11. fn();
  12. console.log(x, y); // 10 100