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,让其指向具体的值10var n = 10;//创建变量m,但不未赋值,默认指向undefinedvar m;console.log(n,m);//=> 输出10 undefinedlet a = 100;//基于let创建一个叫做a的变量,值100a = 200; //修改值console.log(a);//=>输出200const b = 1000; //基于const创建变量b,值1000b = 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创建函数
- 函数的三种角色【会在函数章节再来细谈】
- 普通函数(作用域和作用域链)
- 构造函数(类/实例/原型和原型链)
- (在函数在作为构造函数执行时,会创建一个实例,此时的函数自身相当于一个类)
- 普通对象(键值对/属性值属性名)
- 函数的三种角色【会在函数章节再来细谈】
- Class 类【堆内存】 class创建类(ES6中创建自定义类的新语法)
- 导入模块 import导入模块【也可以创建变量】
- Symbol (1000) 创建一个唯一值
变量提升
当前作用域中JS 代码自上而下执行之前 浏览器会吧所有带有var / function关键字的进行提前声明或定义
var 关键字只是提前声明了一下 带function 关键字的在变量提升阶段吧声明和定义都完成了
//变量提升 => var x; var y; fn = xx001(堆地址)console.log(x); //undefiedvar x = 3, y = 4;console.log(x);//3console.log(fn);// ƒ fn() {}function fn() {}
定义变量时 带var 和不带var区别?
console.log(a); //undefiedvar a = 12;console.log(a); //12console.log(window.a); //12
console.log(a); //undefieda = 12;console.log(a); //12console.log(window.a); //12
带var:在当前作用域中声明一个变量 如果是全局作用域 也就相当于给全局作用域设置一个属性叫a 也就是在window上添加了一个属性a
不加var : a 只是Window的一个属性 (把window省略了) 不是严格意义的变量
只对等号左边的进行变量提升
- = 赋值 左边是变量 右边永远是值
- = 右边也可以是三元表达式 a = b?c:d 但也是先运算出结果在赋值
- = 右边也可能是函数 这样的叫:匿名函数:函数表示式(把函数当做一个值)
console.log(fn) //undefinedvar fn = function(){}console.log(fn) //函数本身
sum(); //TypeError: sum is not a functionvar sum = function(){}// =>上面代码相当于直接输出sum 和console一样 但此时sum还没定义 所以报错fn(); // aaafunction fn(){console.log("aaa");}fn(); // aaa//这种写法 fn已经提升并定义 所以 fu()在上或在下都可以运行
在真实项目中 应用这个原理 我们创建函数时可以用函数表达式 这样更严谨因为只能对等号左边的进行提升,所以变量提升完成后 当前函数只是声明了,没有定义 想要执行函数只能放在赋值的代码之后执行,这样让我们代码更有逻辑
不管条件是否成立都要进行变量提升
var aa = 1;function bb(){//变量提升: 私有变量: var aa ,var baa = 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)最后是4fn() // => 4function fn(){ console.log(1)} //(0x001)fn() // => 4function fn(){ console.log(2)} //(0x002)fn() // => 4var fn = 13 // => fn=13fn() //这里将变成 : 13() 所以会报错 fn is not a function 停止运行function fn(){ console.log(3)} //(0x003)fn()function fn(){ console.log(4)} //(0x004)fn()
函数优先
每遇到一个 var 关键字的变量声明,首先会查询当前作用域之前是否已经有了该名称的变量,如果是,则会忽略该声明;如果没有则把该变量声明提升
变量声明和函数声明都会被提升,那么在重复声明的情况下
预解析时函数首先被提升,然后才到变量。
fn(); // 1var 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(); // foo1console.log(foo2); // undefinedfoo2(); // TypeError: foo2 is not a functionfunction 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);
function a () {}等同于 var a = function() {};
var a = 1; // 定义一个全局变量 afunction foo() {// 首先提升函数声明function a () {}到函数作用域顶端// 然后function a () {}等同于 var a = function() {};最终形式如下var a = function () {}; // 定义局部变量 a 并赋值。a = 10; // 修改局部变量 a 的值,并不会影响全局变量 aconsole.log(a); // 打印局部变量 a 的值:10return;}foo();console.log(a); // 打印全局变量 a 的值:1
暂时性死区
ES6 明确规定,代码块({})中如果出现 let 和 const 声明的变量,这些变量的作用域会被限制在代码块内,也就是块级作用域
console.log(a); //undefinedvar a = 1;console.log(b); //报错 Cannot access 'b' before initializationlet 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 则会变量提升到全局 修改全局都aconsole.log(a); // undefineda = 3;console.log(a); // 3}console.log(a); //1 如果是var a的值被修改成3
let 和 const 声明变量之前不可以使用这些变量,这就是所谓的 暂时性死区。
总之:let \ const必须先声明在使用
初步了解执行上下文、作用域链
全局上下文是最外层的上下文,也就是我们常说的window ,通过var定义的全局变量和函数都会成为window对象的属性和方法 ,通过let、const创建的顶级声明不会定义在全局上下文中 但作用域链解析效果是一样。
上下文在其代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出才会销毁,例如关闭网页退出浏览器)
作用域链
函数执行形成一个私有作用域(保护私有变量)进入到私有作用域中 首先变量提升(声明过的变量都是私有的) 接下来执行代码
- 执行时遇到变量 如果这个变量是私有 那么按照私有处理即可
- 如果当这个变量不是私有 就向上级查询 一直查到window全局作用域为止 这种机制叫
作用域链
- 如果上级作用域没有这个变量(找到window也没有):
变量= 值 相当于给window设置了这个属性 以后再操作window下就有了
如果直接输出(alert(变量))此时没有就会报错
console.log(x, y); // undefined x 2var x = 10,y = 20; // x = 10;y=20function 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
