[JS]【一】– 变量和值、变量提升、函数提升

核心知识点

  1. JavaScript语言的三部分构成
  2. JavaScript中的变量概述
  3. 创建变量的几种方式和各个特点和区别
  4. 变量提升
  5. 初步了解执行上下文、作用域链

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

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

各变量的特点和区别

  • ES3/5中创建变量用var 通过var定义的全局变量和函数都会成为window对象的属性和方法

  • ES6中创建变量用let、const(与var的区别在于变量提升)通过let、const创建的顶级声明不会定义在全局上下文中 但作用域链解析效果是一样

  • let创建的是 变量 ,只不过他的指针指向可以随意的修改。

  • const创建的是 看上去像常量的变量 ,只不过他的 **【指针指向一旦确定,就不能再修改。】所以const创建的变量值是不允许被修改的 **【看起来像但并不是常量】
    const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个引用类型(对象或数组)声明为常量可能会被修改

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

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

  • 导入模块 import导入模块【也可以创建变量】

  • Symbol (1000) 创建一个唯一值

变量提升

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

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

 //变量提升 =>  var x; var y; fn = xx001(堆地址)   
  console.log(x); //undefied
  var x = 3, y = 4;
  console.log(x);//3
  console.log(fn);// ƒ fn() {}
  function fn() {}
复制代码

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

 console.log(a);   //undefied
 var a = 12;
 console.log(a); //12
 console.log(window.a); //12
复制代码
 console.log(a);   //undefied
  a = 12;
 console.log(a); //12
 console.log(window.a); //12
复制代码

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

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

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

  • = 赋值 左边是变量 右边永远是值]

  • = 右边也可以是三元表达式 a = b?c:d 但也是先运算出结果在赋值

  • = 右边也可能是函数 这样的叫:匿名函数:函数表示式(把函数当做一个值)

    console.log(fn) //undefined 
    var fn = function(){} 
    console.log(fn) //函数本身
    复制代码
    sum(); //TypeError: sum is not a function
    var sum = function(){}
    // =>上面代码相当于直接输出sum 和console一样 但此时sum还没定义 所以报错
    fn(); // aaa
    function fn(){console.log("aaa");}
    fn(); // aaa
    //这种写法 fn已经提升并定义 所以 fu()在上或在下都可以运行
    复制代码

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

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

 var aa = 1;
 function bb(){
 //变量提升: 私有变量: var aa ,var b
 aa = 33;
 console.log(aa); //33 =>这里是函数作用域下第一次修改aa的值
     if(1 === 1){
        var b = 4; 
        var aa = 44
        // aa = 44  如果不声明var 这里将修改全局都aa 
      }
     console.log(aa,b);//44 4 =>第二次修改aa的值 b是4
  } 
  bb()
  console.log(aa);  //1 全局  如果上面不声明var 这里值将为44
复制代码

关于重名的处理

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

// 变量提升 : fn = (0x001)=(0x002)=(0x003)=(0x004)最后是4
fn() // => 4
function fn(){ console.log(1)} //(0x001)
fn() // => 4
function fn(){ console.log(2)} //(0x002)
fn()  // => 4
var fn = 13   // => fn=13
fn() //这里将变成 : 13() 所以会报错 fn is not a function 停止运行
function fn(){ console.log(3)} //(0x003)
fn() 
function fn(){ console.log(4)} //(0x004)
fn()
复制代码

函数提升

函数优先

每遇到一个 var 关键字的变量声明,首先会查询当前作用域之前是否已经有了该名称的变量,如果是,则会忽略该声明;如果没有则把该变量声明提升

变量声明和函数声明都会被提升,那么在重复声明的情况下

预解析时函数首先被提升,然后才到变量。

fn(); // 1 
var fn; 
function fn() { console.log(1); } 
var fn = function() { console.log(2); } 
fn(); // 2
复制代码

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

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

console.log(foo1); // [Function: foo1]
foo1(); // foo1
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
function foo1 () {
	console.log("foo1");
};
var foo2 = function () {
	console.log("foo2");
}
// 这里可能会有人有疑问? 为foo2会报错,不同样也是声明?
// foo2在这里是一个函数表达式且不会被提升
复制代码

一个函数提升案例

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

暂时性死区

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

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

let 和 const 也会有类似“提升“的行为,但跟 var 不同的是,提升的时候变量值并不会默认赋值为 undefined,并且会禁止在声明之前使用这些变量,这就是所谓的暂时性死区。

总之:let\const必须先声明在使用

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

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

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

作用域链

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

  1. 执行时遇到变量 如果这个变量是私有 那么按照私有处理即可
  2. 如果当这个变量不是私有 就向上级查询 一直查到window全局作用域为止 这种机制叫作用域链
  • 如果上级作用域没有这个变量(找到window也没有):
    变量= 值 相当于给window设置了这个属性 以后再操作window下就有了
    如果直接输出(alert(变量))此时没有就会报错
 console.log(x, y); // undefined x 2
 var x = 10, 
     y = 20; // x = 10;y=20
 function fn() {
    //开辟堆内存 形成一个私有作用域 
    // 变量提升 var x  (私有变量)
    console.log(x, y); // x为undefined  y是20【全局】
    var x = y = 100; // x =100 (私有)  y = 100 【全局】
    console.log(x, y); // 100 100
   }
 fn();
 console.log(x, y); // 10 100
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享