文章目录
一,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的变量的区别
- 在全局代码中,加var的变量会得到提升,不加var的变量不会提升;
console.log(a);// a is not defined
a = 110; //没有加var所以变量没有提升
- 不管加没加var的全局变量,都会成为window的属性
var a = 1;
b = 2;
console.log(window.a) //输出1
console.log(window.b) //输出2
- 没有加var的变量,只能作为全局变量,只要是全局变量,肯定是window的属性;
function f() {
a = 666;
}
f() //执行函数,将666赋值给a
console.log(window.a) //输出666
- 加var的局部变量,不会作为window的属性;
function f() {
var a = 666;
}
f()
console.log(a) //a is not defined;函数执行后没有将变量放入EO(G)中;
- 访问一个全局对象(GO)上不存在的属性,结果就是undefined;
console.log(window.a) // 输出undefined,因为a不存在与全局对象
3,使用let声明变量、和使用const声明常量
- let声明变量的特点:
- let声明的变量没有提升
console.log(a);
let a = 110; // Cannot access 'a' before initialization
- let 配合 {} 也可以形成块级作用域
if(true){
var a = 110; // 全局变量
// b只能在当前的{}中被访问到 出了块就访问不了
let b = 666; // let + {} 形成**块级作用域**
}
console.log(b); // b is not defined(b没有定义)
- 使用let声明的变量不会挂载到GO上
let a = 110;
console.log(window.a); // undefined 访问一个对象上没有属性,得到的就是undefined
- 使用let不能重复声明
let a = 1;
let a = 2;
console.log(a); // Identifier 'a' has already been declared(标识符“a”已经声明)
- 使用const声明常量的特点:
- const声明的是一个不会改变的值
const a = 110; // 定义了一个常量(不会改变的量)
a = 666;
console.log(a); // TypeError: Assignment to constant variable.(类型错误:对常量变量的赋值。)
- 也没有提升
- 也会形成块级作用域
- 使用const声明的常量不会挂载到GO上
- 使用const不能重复声明
- const在声明常量时,必须赋值
声明变量使用let ,声明常量使用const,一般不要使用var
四,JS中的数据类型
为了更加合理使用内存空间,所以针对不同的数据,分配不同的空间。
- 基本数据的类型:
(1)number 数字;var a = 110; int a = 110;
(2)string 字符串;JS中不分字符和字符串 都叫字符串;
(3)boolean;true和false 布尔类型;
(4)undefiend;表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性;
(5)null; 表示没有值; - 引用数据类型:
(1)object 对象
(2)array 数组
(3)function 函数 在JS中函数也是一种数据类型;
1,基本数据类型
(1)number 数据类型
- number是一个数据类型,这个数据类型对应的值有无数个。
- 在JS中数据类型是不区分整数和小数的,都是number;
- 查看数据类型可以使用typeof来查看,
console.log(typeof a) //查看a的数据类型;
- 数值是有自己的最大范围和最小范围的;通过
console.log(Number.MAX_VALUE)//查看数据的最大值;
console.log(Number.MIN_VALUE)//查看数据的最小值;
- 可以使用不同的进制来表示(2进制、8进制、16进制…);
- NaN;表示Not a Number 不是一个数字
- JS中不要对小数进行计算,结果往往不对,一般先转换为整数;
- typeof是运算符 + - * / 都是运算符
- Number叫类,也叫构造器,也叫函数;
(2)string 字符串
- 在JS中,字符串要使用单引号或者双引号包起来,要不JS会把它当做变量;
- 单双引号使用只能是外单内双,外双内单
- string字符串也是无数个;
(3)boolean 布尔类型
- 对应的值只有两个:一个true,一个false;
- 要注意true和True是不一样的,在JS中是区分大小写的;
(4)undefiend
- undefiend是一个数据类型,这种数据类型对应的值是undefiend;
- 当一个变量没有赋值,它的值是undefiend,这个值的类型是undefiend;
(5)null
- 就表示变量值为空
2,引用数据类型
(1)object 对象
var obj = { // 对象 就是 集合,放一堆的数据,而变量里只放一个数据
name:"wangcai",
age:100
}
console.log("name" in obj); // in是一个运算符,用来判断一个对象有没有一个属性
(2)array 数组
(3)function 函数
3,JS中的数据类型的转换
(1)隐式类型转换
不知不觉的就把类型转换了
- +叫运算符,123叫操作数 ,"abc"也叫操作数;
- 如果一个运算符有两个操作数,那么这个运算符就叫二元运算符,或者二目(双目)运算符;例如: + - =…
- 如果一个运算符只有一个操作数,这个运算符叫一元运算符,或者单目运算符;例如: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: 作用域链
- 代码分为两大类:
- 全局代码:函数以外的代码叫做全局代码;
- 局部代码(函数代码):一个函数就是一个局部代码;
- 全局执行上下文:
全局代码在执行时,就会产生全局的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,全局执行上下文
代码执行过程是一步步进行;
- 当获取一个数据,首先判断它是否为全局变量,上图代码中,首先将全局变量n存放于EC(G)中的变量对象(VO)中,在全局变量(GO)中也会存放一份;
- 当执行console.log(n);输出:100;
- 当执行console.log(window.n);同样输出:100;
- 执行console.log(m);输出200 // 因为没有加var,所以去GO中找;
- 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执行流程
- 在执行代码代码 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执行流程之——什么是闭包
代码执行完整过程:
- 第一步提升变量(找加var的变量或者声明函数),此时提升的变量只是变量名,并没有赋值;将i、A函数(函数体放入单独的堆(0x000fff)中)、y、B函数(函数体放入堆(0x111fff)中)都放入EC(G)中的VO中;
- 第二步执行代码,先给变量 i 赋值,然后函数声明部分跳过,接着先执行函数A();每执行一个函数都会产生一个局部的EC,所以执行函数A时会产生一个EC(A);
- 在执行函数A过程中,第一步先看是否有提升,第二步开始,代码中,有var i = 10 所以第一步:先将 i (局部变量)、函数x(整个函数体都提升,函数体放进单独的堆中,此堆会有一个地址(0x222fff))、存入EC(A)中的AO中;第二步代码执行:先将变量 i 赋值,函数声明x先跳过,最后执行语句return x;回去调用函数x的方法,先去EC(A)中找x,能找到,此时找到的是x对应的地址,函数不加括号表示只拿到方法,加括号时表示函数需要执行,第181行代码执行结果就是将返回的x对于的地址赋给y;此时y对应的就是地址;
- 执行182行代码:执行y(),先在EC(G)中找到y,能找到,此时对应的是一个地址,根据地址找到对应的堆(0x222fff),执行堆中的语句,语句中有变量 i 需要先去父级EC(A)中找变量 i ,能找到,此时堆中语句就会输出 i 的值,然后之前创建的EC(y)会销毁;
- 代码继续执行,函数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,函数定义
- 定义 = 声明 + 赋值
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