JS(JavaScript)相关知识——详细总结

一,JS是什么

  • JS:全称JavaScript,简称JS,是一种具有函数优先的轻量级,解释型或即时编译型的高级编程语言

编程语言分类:
1)前端语言:JS(运行在浏览器上面)…
2)后端语言:Java C++ Python Go JS C#…

1,JS与html、css对比

  • 相同点:都可以运行在浏览器上面,他们的运行环境是浏览器;
  • 不同点:JS是编程语言,而另外两种不是编程语言,JS的运行环境不只浏览器,也可以是其他环境;

2,JS能做什么

1)开发网站
2)开发app
3)小程序
4)游戏开发 小程序游戏 网页游戏
5)写后端 node.js
6)嵌入式 (一般是用C语言,但JS也可以写)
7)区块链

3,JS的三种写法

1)内部写法:把JS代码写在html文件中,一般在学习中写在script标签中;如下:

<script>
    var a = 110;
    console.log(a)
</script>

2)外部写法:把JS代码写在单独创建的JS文件中,然后在html文件中通过script标签引入,这个在项目开发的时候都这样写;引入如下:

<script src="./文件名.js"></script>

3)行内标签:把JS代码写在开始标签中,当成开始标签的属性;例如button标签中引用onclick()点击事件:

<button onclick="console.log(888)">点我</button>

二,JS的基本语法

1)JS 是区分大小写的,例如var a = 1; var A = 1; 是不同的;
2)在写代码时候,JS会忽略空白符(空格、换行、tab键);
3)语句分号可加可不加;
4)注释:单行注释( // ) ;多行注释( / * 注释 * /注释是给程序员看的
5)标识符和关键字; var a = 10; var 是关键字;a是变量名标识符;

1. 什么是代码段

  • 一个script标签就是一个代码段。
  • JS代码在执行时,是一个代码段一个代码段执行。

三,JS中的变量

(1)数据:打开一个软件或者浏览器时,会有很多数据,也叫状态,而这个状态会保存在两个地方,分别是:内存 硬盘

  • 内存:内存中只是暂时存放的数据,当电脑关机之后,数据就会被清除;
  • 硬盘:可以把数据在电脑关机之后不丢失;

(2)变量:简单说就是内存的一个空间;

  • 变量名:内存空间的别名 对变量名的操作就是对内存空间的操作
  • 变量值:存储在内存空间中的状态(数据)

在JS中定义一个变量:
var a = 110; // 定义了一个变量 变量的名是a 变量的值是110;

1,变量的分类

1)全局变量:在函数内外都能访问到
2)局部变量:只能在函数内部访问到
区别这两个的分界点就是“函数”;只要把变量写在函数里面就是局部变量,只要写在函数外面就是全局变量;

2,加var和不加var的变量的区别

  1. 在全局代码中,加var的变量会得到提升,不加var的变量不会提升;
console.log(a);// a is not defined
a = 110;  //没有加var所以变量没有提升
  1. 不管加没加var的全局变量,都会成为window的属性
var a = 1;
b = 2;
console.log(window.a) //输出1
console.log(window.b) //输出2
  1. 没有加var的变量,只能作为全局变量,只要是全局变量,肯定是window的属性;
function f() {
    a = 666;
}
f()  //执行函数,将666赋值给a
console.log(window.a)  //输出666
  1. 加var的局部变量,不会作为window的属性;
function f() {
    var a = 666;
}
f()
console.log(a)  //a is not defined;函数执行后没有将变量放入EO(G)中;
  1. 访问一个全局对象(GO)上不存在的属性,结果就是undefined;
console.log(window.a) // 输出undefined,因为a不存在与全局对象

3,使用let声明变量、和使用const声明常量

  • let声明变量的特点:
  1. let声明的变量没有提升
console.log(a);
let a = 110;  //  Cannot access 'a' before initialization
  1. let 配合 {} 也可以形成块级作用域
if(true){
         var a = 110; // 全局变量
         // b只能在当前的{}中被访问到 出了块就访问不了
         let b = 666; // let + {} 形成**块级作用域**
     }
     console.log(b); // b is not defined(b没有定义)
  1. 使用let声明的变量不会挂载到GO上
let a = 110;
console.log(window.a); // undefined 访问一个对象上没有属性,得到的就是undefined
  1. 使用let不能重复声明
let a = 1;
let a = 2;
console.log(a); // Identifier 'a' has already been declared(标识符“a”已经声明)
  • 使用const声明常量的特点:
  1. const声明的是一个不会改变的值
const a = 110;  // 定义了一个常量(不会改变的量)
a = 666;
console.log(a); // TypeError: Assignment to constant variable.(类型错误:对常量变量的赋值。)
  1. 也没有提升
  2. 也会形成块级作用域
  3. 使用const声明的常量不会挂载到GO上
  4. 使用const不能重复声明
  5. const在声明常量时,必须赋值
    声明变量使用let ,声明常量使用const,一般不要使用var

四,JS中的数据类型

为了更加合理使用内存空间,所以针对不同的数据,分配不同的空间。

  1. 基本数据的类型:
    (1)number 数字;var a = 110; int a = 110;
    (2)string 字符串;JS中不分字符和字符串 都叫字符串;
    (3)boolean;true和false 布尔类型;
    (4)undefiend;表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性;
    (5)null; 表示没有值;
  2. 引用数据类型:
    (1)object 对象
    (2)array 数组
    (3)function 函数 在JS中函数也是一种数据类型;

1,基本数据类型

(1)number 数据类型

  1. number是一个数据类型,这个数据类型对应的值有无数个。
  2. 在JS中数据类型是不区分整数和小数的,都是number;
  3. 查看数据类型可以使用typeof来查看,console.log(typeof a) //查看a的数据类型;
  4. 数值是有自己的最大范围和最小范围的;通过
console.log(Number.MAX_VALUE)//查看数据的最大值;
console.log(Number.MIN_VALUE)//查看数据的最小值;
  1. 可以使用不同的进制来表示(2进制、8进制、16进制…);
  2. NaN;表示Not a Number 不是一个数字
  3. JS中不要对小数进行计算,结果往往不对,一般先转换为整数;
  • typeof是运算符 + - * / 都是运算符
  • Number叫类,也叫构造器,也叫函数;

(2)string 字符串

  1. 在JS中,字符串要使用单引号或者双引号包起来,要不JS会把它当做变量;
  2. 单双引号使用只能是外单内双,外双内单
  3. string字符串也是无数个;

(3)boolean 布尔类型

  1. 对应的值只有两个:一个true,一个false;
  2. 要注意true和True是不一样的,在JS中是区分大小写的;

(4)undefiend

  1. undefiend是一个数据类型,这种数据类型对应的值是undefiend;
  2. 当一个变量没有赋值,它的值是undefiend,这个值的类型是undefiend;

(5)null

  1. 就表示变量值为空

2,引用数据类型

(1)object 对象

    var obj = {  // 对象 就是 集合,放一堆的数据,而变量里只放一个数据
        name:"wangcai",
        age:100
    }
    console.log("name" in obj); // in是一个运算符,用来判断一个对象有没有一个属性

(2)array 数组

(3)function 函数

3,JS中的数据类型的转换

(1)隐式类型转换

不知不觉的就把类型转换了

  1. +叫运算符,123叫操作数 ,"abc"也叫操作数;
  2. 如果一个运算符有两个操作数,那么这个运算符就叫二元运算符,或者二目(双目)运算符;例如: + - =…
  3. 如果一个运算符只有一个操作数,这个运算符叫一元运算符,或者单目运算符;例如:typeof、++…
  • 双元运算符要保证两侧操作数的数据类型要一致;
  • 布尔类型也会出现隐式转化,一下的值转化为false,其它值都会转化为true;

0、-0、“ ”、undefiend、null;

隐式转换:

var res = 123 + "abc";  // 123隐式转化成字符串
console.log(res);  // 123abc
console.log(typeof res);  // string

(2)强制类型转换

console.log(parseInt(3.14)); // 把小数转成整数
console.log(parseInt("3.14abc")); // 尝试把小数或非数字转成整数
console.log(parseFloat(3))//解析一个字符串,并返回一个浮点数
console.log(parseFloat("3.14abc"))
console.log(Number("abc123"))  // NaN不是一个数字
console.log(Number("123abc"))  // NaN
console.log(Number("123"))  // 123
console.log(String(123456))  // 转为字符串123456

五,JS代码在执行时的两个阶段

1,预编译阶段

提升

  • 把加var的变量进行提升,提升的是变量声明,变量的赋值不会提升;
  • 把使用function声明的函数进行提升,提升的是整个函数声明;

2,代码执行阶段

代码执行过程是一行一行的执行

以下具体的实例:

console.log(a);  //输出 undefined
var a = 110;
console.log(a); // 110
console.log(g); // undefined
    g(); //输出 undefied()  g is not a function
var g = function () {  // 这里提升的是声明 
        console.log("g...")
    }
var i=0; //全局变量
for(var i=0; i<2; i++){
    console.log(i);//输出0 1 
 }

3,什么是作用域链

作用域链说的是数据的查找机制,找一个数据,先在自己的EC中找,找不到,去父函数所在的EC中找,如果还找不到,就去父函数的父函数EC中找,直到找到全局EC,如果还找不到,就报错了。

4,JS中的同步和异步代码(闭包的使用)

同步:代码的书写顺序和代码的执行顺序一样。
异步:代码的书写顺序和代码的执行顺序不一样。

  • 异步的使用很少的,仅仅是个别使用: 1)事件绑定 2)定时器 3)ajax …
  • 注意JS代码中98%都是同步实现的;

实例代码:

  • 异步代码中的事件绑定:
    异步代码中的事件绑定
  • for循环中的this使用
    在这里插入图片描述

5,有关运算符问题

(1)有关++(- -)运算符的问题

  • ++在前和++在后,对于i的值来说,都是加1操作,整体也有一个值 此时++在前和++在后就不一样了;
  • ++在前时候,输出的是一个新值,相当于先执行操作;++在后时候,输出的是一个旧值,相当于后执行操作;
  • 反之 (- -) 运算符也是同样的操作;
 var a=3
 var b=4
 console.log(a++)  //输出 3  输出的是个旧值,就是先给a输出,然后在执行加1;
 console.log(++a)  //输出 5  输出的是个新值,就是先给a加1在输出;
 console.log(++a+b)  //输出 10  a目前为5,先进行加1操作,再加b的值,结果为10;
  console.log(--a-b)  //输出 1  a目前是6,先进行减1操作,再减b的值,结果为1;
console.log(a);  // undefined
    console.log(b);  // undefined
    for (var a=1; a<2; a++){
        for(var b=1; b<2; b++){
        }
    }
    console.log(a); // 输出2
    console.log(b); // 2
console.log(a);// 输出 a is not defined
    a = 110;
    console.log(a); 110

(2)逻辑或(&&) 、逻辑与(||)、逻辑非(!)

  • 逻辑或(&&):是双元运算符,只有两边操作数都是真才是真;
  • 逻辑与(||):也是双元运算符,只有两边操作数都是假才是假;
  • 逻辑非(!):单目运算符,只有一个操作数,如果是真整体就是假,如果为假整体就是真;

六,JS中的执行上下文(Execute Context)

执行上下文:简称EC;
作用:为代码提供数据;
EC: 执行上下文
AO:活动对象(存储了局部执行上下文中的数据)
VO:变量对象(存储全局执行上下文中的数据)
GO:全局对象 window
ECStack: 执行上下文栈
Scope: 作用域
ScopeChain: 作用域链

  • 代码分为两大类:
  1. 全局代码:函数以外的代码叫做全局代码;
  2. 局部代码(函数代码):一个函数就是一个局部代码;
  • 全局执行上下文:
    全局代码在执行时,就会产生全局的EC,或者叫做EC(G); // G:global
  • 局部执行上下文:
    函数代码执行时候,就会产生局部的EC,调用一个函数就会产生一个EC,调用6个函数,就会产生6个EC;
  • EC栈是什么东西:
    所谓栈:就类似于一个杯子,代码执行时,每产生一个EC就会放入杯子,就相当于,向杯子里面放鸡蛋,但是具有一定的规则:先进后出原则;
var a = 100;
    function f() {
        console.log("f...");
    }
    f(); //函数执行一次就会产生一个局部的EC
    f(); //函数执行一次就会产生一个局部的EC
    f(); //函数执行一次就会产生一个局部的EC

1,全局执行上下文

全局执行上下文
代码执行过程是一步步进行;

  1. 当获取一个数据,首先判断它是否为全局变量,上图代码中,首先将全局变量n存放于EC(G)中的变量对象(VO)中,在全局变量(GO)中也会存放一份;
  2. 当执行console.log(n);输出:100;
  3. 当执行console.log(window.n);同样输出:100;
  4. 执行console.log(m);输出200 // 因为没有加var,所以去GO中找;
  5. console.log(x);报出x没有定义 // 在VO和GO中都没有存在,所以没有定义;但如果是console.log(window.x)就会输出undefined;// 访问一个全局对象(GO)上不存在的属性,结果就输出undefined;

1. 当获取一个没有加var的变量时候,首先它只能是一个全局变量,它将存放于GO中;而获取一个带有var的全局变量时候,它将存放于GO和VO中

2. 只要加var,变量肯定会提升;

3. 加var的局部变量只会提升到函数内部最前面的;

2,初步了解全局堆栈执行流程

初步了解全局堆栈问题

  • 内存分为 堆内存 和 栈内存:
  • 在JS中,基本数据类型存放在栈中,引用数据类型存放在堆中;

—上图代码中: 变量a、b在预编译阶段,会先将a、b变量存放在VO和GO中,而在代码执行过程中才将数值赋给a、b,而函数f 和数组 arr会将地址存放在VO和GO中,而具体的函数体会单独存在于堆中,并且每个堆还会有自己的地址,调用函数时,通过在VO或者GO中找到函数的地址,进而在堆中找到相应的函数体;

3,初步了解ECStack执行流程

初步了解ECStack

  • 在执行代码代码 f(111) 时,首先将实参111传递给函数f的形参a,然后执行函数体,输出111;
  • 找函数console.log(a) 中的a,首先去自己的EC中找,如果没有就去上一级(父级)EC中找。而对于 f 来说它的父级EC就是EC(G);
  • 找下面一行代码console.log(a)中的a,此时就不能去EC(f) 中找,因为此时的EC(f)已经出栈了,只能去EC(G)中找,如果找不到就去GO中找,还找不到的话就会报错了;

4,ECStack执行流程之——什么是闭包

什么是闭包
代码执行完整过程:

  1. 第一步提升变量(找加var的变量或者声明函数),此时提升的变量只是变量名,并没有赋值;将i、A函数(函数体放入单独的堆(0x000fff)中)、y、B函数(函数体放入堆(0x111fff)中)都放入EC(G)中的VO中;
  2. 第二步执行代码,先给变量 i 赋值,然后函数声明部分跳过,接着先执行函数A();每执行一个函数都会产生一个局部的EC,所以执行函数A时会产生一个EC(A);
  3. 在执行函数A过程中,第一步先看是否有提升,第二步开始,代码中,有var i = 10 所以第一步:先将 i (局部变量)、函数x(整个函数体都提升,函数体放进单独的堆中,此堆会有一个地址(0x222fff))、存入EC(A)中的AO中;第二步代码执行:先将变量 i 赋值,函数声明x先跳过,最后执行语句return x;回去调用函数x的方法,先去EC(A)中找x,能找到,此时找到的是x对应的地址,函数不加括号表示只拿到方法,加括号时表示函数需要执行,第181行代码执行结果就是将返回的x对于的地址赋给y;此时y对应的就是地址;
  4. 执行182行代码:执行y(),先在EC(G)中找到y,能找到,此时对应的是一个地址,根据地址找到对应的堆(0x222fff),执行堆中的语句,语句中有变量 i 需要先去父级EC(A)中找变量 i ,能找到,此时堆中语句就会输出 i 的值,然后之前创建的EC(y)会销毁;
  5. 代码继续执行,函数B先跳过,执行187行代码,执行函数B,先创建一个局部的EC(B),同样分两步进行,先提升后代码执行,B中有一个局部变量 i ,然后将变量 i 存入EC(B)中的AO中,接着执行y(),同样先创建一个局部的EC(y),而函数y对应的是一个地址(0x222fff),通过地址找到存放函数体的堆,执行堆中的函数体,此时的变量 i 先去父级EC(A)中找,能找到,执行函数体,输出 i 的值;此时局部EC(y)销毁,EC(B)销毁;代码执行结束

- 闭包是什么

  • 执行函数时候,按正常执行流程来说,会产生一个局部的EC,但是在函数执行完成后,所产生的局部EC会自动销毁。
  • 但是如果一个局部EC中的一个局部变量又单独对应了一个堆,而且这个堆的地址也在EC(G)中存在,那么当函数执行完成之后,这个局部的EC不能被释放,此时就形成了一个不被释放的栈空间(局部EC),叫做闭包
  • 调试面板中也会生成相应Closure,就是所谓的闭包,如下:
    调试面包中的闭包

闭包的好处与坏处:
好处:延长了一个局部变量的生命周期;对栈空间的数据具有保护、保存的效果;
坏处:当y()调完之后,EC(y)被销毁,但是 i 的栈空间还需要在,此时就只能常驻内存,导致内存泄露

关于闭包的实例

  • 上图代码执行过程中就生成了闭包,因为堆(0x1f)中的 i 变量的值存在于它的父级EC(fn)中,此时 i 还不能被销毁(还有用的),因此就形成了一个闭包,进而也延长了EC(fn)的生命周期;

七,JS中的函数定义

在JS中定义函数有两种形式:

  1. 函数定义
  2. 函数表达式

1,函数定义

  • 定义 = 声明 + 赋值
function f() //  声明
{
    console.log("hello")
    console.log("js")
    console.log("vue")
}  //  赋值

f 叫函数名, ()是函数特有的标识, {} 叫函数体
f ( ); 表示函数调用,调用函数时,就会把函数体中的代码都执行了

  • 函数的返回值
  • 一个函数如果没有返回值,默认返回undefiend
 function f() {
    return 666;
}
var a = f(); // 函数调用,函数的返回值是返回到了函数调用处
console.log(a); // 输出666
  • 给函数传递数据
 function f(a,b) {  // a b叫形式参数(形参)形参就是函数内部的局部变量
        return a+b;
    }
    var r = f(1,2); // 1 2叫实际参数(实参),函数调用的过程就是实参向形参赋值的过程
    console.log(r); // 输出 3 

(1)立即调用的函数表达式

//当在函数外部加一个括号,然后在执行函数时,将会立即调用函数;
 (function f() {
     console.log("f...")
 })();  // IIFE  立即调用函数表达式;运行结果就是f...

在函数前面加+ - ! 号是也可以实现立即调用函数表达式的效果的;

var t = (function (i) {
     return function () {
         alert(i *= 2);  // i = i*2
     }
 })(2);
 t(5);  //输出4  t中的参数5在这里是没有用到的;

八,JS中的错误类型

1,五中错误类型

(1)语法错误

  • 在函数预编译的时候就出现的错误,导致代码没有执行的机会,这种错误最好解决;
  • 错误提示如下:
//Uncaught SyntaxError: Function statements require a function name(未捕获的SyntaxError:函数语句需要函数名)
function () {
}

(2)引用错误

  • 访问一个没有定义的变量就会发生引用错误;将变量定义好再引用就OK;
  • 如果发生引用错误后面的代码就不在执行;
  • 错误提示:
console.log("start")
    //Uncaught ReferenceError: a is not defined;(未捕获的ReferenceError:未定义)

(3)类型错误

  • 使用类型不当,在代码运行时候发现的,就是你所提供的类型不能够使用,此时根据需要修改类型即可;
  • 同样类型错误后面的代码不再执行;
  • 错误提示:
console.log("start")
var f = 110;
f(); // TypeError: f is not a function(类型错误:f不是一个函数)
console.log("end")

(4)范围错误

  • 使用容器时候范围指定不当;
  • 错误提示:
var arr = ["a","b","c"];  // 定义数组方式1
var arr2 = new Array(10); // 定义数组方式2   10表示数组中可以存储10个数据
var arr3 = new Array(-5); // Uncaught RangeError: Invalid array length(未捕获的距离错误:无效的数组长度)

(5)逻辑错误

  • 以上四种错误是在控制台进行报错的,而这个错误控制台不能报错的,可以通过debugger调试错误;

2,异常捕获和处理

异常不是错误,异常是可能出错的代码;

基本语法:

try{
       // 放可能出错的代码
     }catch(e){
       // 如果上面的代码错误了,就到这里进行错误的处理,其中e表示错误对象
       // 它里面包含了错误信息。通过查看e,可以知道它到底出了什么错误
      }finally{
            // 无论对错,代码都会执行到此处,在JS中用的不多;
        }
  • 程序员在代码维护中可以使用捕获来主动抛出错误:
try{
        var a = 0;
        console.log(a);
        if(a == 0){
            // 程序员主动抛出一个错误
            throw new Error("不能为0"); // 抛出错误   catch会捕获到
        }
    }catch (e) {
        console.log("友情提示:这里有点小问题,工程师正在解决中...",e)
    }
//运行结果:0   test.html:25 友情提示:这里有点小问题,工程师正在解决中... Error: 不能为0

猜你喜欢

转载自blog.csdn.net/qq_44830054/article/details/107363888