基本数据类型:Number/String/Boolean/Null/Undefined
引用数据类型:分两种:
1)Object(Object/Array/Date/RegExp/Match)
2)Function
1.基本数据类型存储在栈内存中
2.数据存储时,基本数据类型在变量中存的是值,引用数据类型在变量中存储的是空间地址
1.堆内存和栈内存是浏览器形成的两个虚拟内存
2.栈内存主要是用来存储基本数据的值,栈内存是一种简单的存储,但是存储的数据都是有范围上线的,一旦超过,就会造成栈溢出
3.堆内存主要是用来存储引用数据类型的
堆内存:存储引用类型,对象类型就是键值对,函数就是代码字符串
堆内存释放:将引用类型的空间地址变量赋值成null,或者没有变量占用堆内存了,浏览器就会释放掉这个地址
栈内存:提供代码执行的环境和存储基本类型值
栈内存释放:一般当函数执行完后,函数的私有作用域就会被释放掉
1.Null表示为空,用来占位,但是以后可以重新赋值,Undefined表示为定义
2.Null表示空对象指针,可以给变量赋值为Null,来清空变量,可以用来释放堆内存
3.
如果变量未定义,那么默认存储值为undefined
如果对象某个属性不存在,获取到的值也是undefined
如果函数的形参没有对应的实参,那么形参默认的存储值也是undefined
如果函数没有返回return的值,那么默认返回还是undefined
1.JS的主要用途是和用户互动以及操作DOM,如果不是单线程,那么就会造成很复杂的同步问题,所以JS只能被设计成单线程
2.为了利用多核CPU的计算能力,H5提出了web worker标准,允许JS脚本创建多线程,但是子线程完全受主线程控制,并且无法操作Dom,所以这个新标准并没有改变JS单线程的本质
单线程是指一次只能完成一个任务,如果在同时间执行多个任务,那么这些任务就得排队,只有前一个任务执行完成,才会执行下一个任务,但是如果有一个任务执行时间很长,就会导致后面的任务一直处于等待状态,这样就会造成用户体验问题,所以为了解决这个问题,JS将任务执行模式分为同步(Async)和异步(Await)
1.同步模式:同步模式就是前一个任务执行完成之后,再执行下一个任务,程序的执行顺序与任务的排列顺序是一致的,也是同步的
2.异步模式:异步模式就是每一个任务有一个或多个回调函数,前一个任务结束后,不是执行队列上的最后一个任务,而是执行回调函数,后一个任务不需要等前一个任务的回调函数执行完成之后再执行,所以程序执行顺序与任务的排列顺序是不一致的,也是异步的
JS事件循环,Event Loop
1.同步任务和异步任务分别进入不同的执行场所,同步任务进入主线程,异步任务进入Event Table并且注册回调函数
2.当执行的事情完成之后,EventTable会将这个函数植入任务队列,等待主线程的任务执行完毕
3.当栈中的代码执行完毕,执行栈中的任务为空时,就会读取任务的回调
4.如此循环,就形成了事件循环的机制
JS事件表格,Event Table
1.Event Table 可以理解为一张事件和回调函数的对应表
2.Event Table 用来存储JS中的异步事件以及对应的回调函数的列表
3.当执行的事件完成时,Event Table会将这个回调函数移入宏任务队列或微任务队列
1.JS是单线程语言,简单的说就是只有一条通道,那么在任务多的情况下,会出现拥挤情况,这种情况下就产生了多线程,实际上这种多线程是通过单线程模仿的,也就是假的,那么就会用到同步任务和异步任务
2.宏任务是由node和浏览器发起的,微任务先执行,宏任务后执行
宏任务具体事件:setTimeout,setInterval,XMLHttpRequest,setImmedia,I/O,UI rendering等等
微任务是由JS引擎发起的,微任务具体事件:Promise,Process.nextTick,Object.observe,MutationObserver等等
1.基本数据类型的值放在栈区,可以直接访问和修改,并且相互之间不会影响
2.引用数据类型的地址放在栈区,值放在堆区,所以当你进行赋值操作的时候,实际上赋值的是地址
浅拷的方法:
1.我们可以自己写一个浅拷贝,实现原理是通过Object.protype.toString.call获取数据类型,通过for循环判断,用数据私有化属性hasOwnProperty进行赋值
2.Object.assign()
3.Array.prototype.slice()
4.数据的浅拷贝用 ... 运算符和concat()
深拷贝的方法:
把一个对象中所有的属性或者方法一个一个的找到,并且在另一个对象中开辟对应的空间,然后一个一个的存储到另一个对象中
1.JSON.parse() / JSON.stringify()
浅拷贝只赋值对象的第一层属性,深拷贝可以对对象的属性进行递归赋值
浅拷贝就是赋值,相当于把一个对象中所有的内容赋值一份给另一个对象,直接赋值,或者说就是把一个对象的地址给了另一个对象,他们指向相同,两个对象之间的共同属性或者方法都可以使用
Promise的使用场景:处理异步回调,多个异步函数同步处理,异步依赖异步回调,封装统一的入口办法或者错误处理
Promise是JS中进行异步操作的新解决方案,Promise是一个构造函数,Promise对象来封装一个异步操作并可以获取成功和失败的返回值,Promise支持链式调用
Promise异步操作有三个状态,分别是pending(进行中)reslove(成功)reject(失败)任何其他操作都不能改变这个状态
状态缺点:
无法取消Promise,一旦新建就会立即执行,无法中途取消。如果不设置回调函数,Promise内部抛出的作物,不会反应到外部,当处理pending状态时,无法得知目前进展到哪一个阶段
then方法接收俩哥哥函数当作参数,分别是成功和失败,两个函数指挥有一个被调用
then支持多次调用
Promise常见的API方法:
.then:得到Promise内部任务的执行结果
.catch:得到Prmise内部任务失败的结果
.finally:无论是成功还是失败,都会返回
.all:按顺序指定多个Promise并且都执行结束之后分别返回结果
.race:
Promise执行顺序:
- console.log(1)
- setTimeout(function(){
- console.log(2)
- }, 0)
- new Promise(function(resolve){ // 这里的回调是同步的
- console.log(3)
- resolve()
- }).then(function(){ // 异步微任务
- console.log(4)
- })
- console.log(5) // 1,3,5,4,2
1.function定义函数,this指向对着调用环境的变化而变化,箭头函数中的this指向是固定不变的
2.function可以定义构造函数,箭头函数不可以,箭头函数不能使用new,也不能使用argument对象,因为箭头函数不存在,如果使用,可以用rest代替
3.由于js内存机制,function级别最高,因为变量提升。因为var定义的变量不能得到变量提升,所以箭头函数一定要定义在调用之前
4.箭头函数不能使用yield命令,所以不能作为Generator函数
5.call()、apply()、bind()等方法不能改变箭头函数中this的指向
闭包是什么:闭包是指有权访问另一个函数作用域中变量的函数。
形成闭包的原因:内部的函数存在外部作用域的引用就会导致闭包。
闭包的作用:
1.保护函数的私有变量不受外部的干扰,形成不销毁的栈内存。
2.把一些函数内的值保存下来,闭包可以实现方法和属性的私有化。
闭包的使用场景:
闭包的缺点:
1.容易导致内存泄漏
2.闭包会携带包含其他的函数作用域,因此会比其他函数占用更多的内存
3.过度使用闭包会导致内存占用过多,所以要谨慎使用闭包
1.创建空对象
2.新建对象执行prototype连接原型
3.绑定this到新对象上
4.执行构造函数
5.返回新对象
在JS中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个prototype属性,它的属性值就是一个对象,这个对象包括了可以由该构造函数的所有实例共享的属性和方法,当使用构造函数新建一个对象的时候,在这个对象的内部将包含一个指针,这个指针指向的构造函数的prototype属性对应的值,在ES5中这个指针,就被成为对象的原型。
ES5中新增了一个方法,叫 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象中去寻找这个属性,这个属性对象又会偶自己的原型,于是就这样一直找下去,这个就是原型链。
原型链的终点是 null,因为Object是构造函数,原型链终点是Object.prototype.__proto__,因为Object.prototype.__proto__ === null // true,所以原型链的终点是null
使用 hasOwnProperty() 方法来判断属性是否属于原型链的属性
1)全局作用域:
最外层函数和最外层函数外面定义的变量拥有全局作用域
所有未定义直接赋值的变量自动声明为全局作用域
所有window对象的属性拥有全局作用域
全局作用域有很大的弊端,比如过多的全局作用域变量会污染全局命名空间,容易引起命名冲突
2)函数作用域
函数作用域声明在函数内部的变量,一般只有固定的代码片段可以访问到
作用域是分层的,内层作用域可以访问外层作用域,但是外层作用域却无法访问内层作用域
3)块级作用域
使用ES6中新增的let和const指令可以声明会计作用域,块级作用域可以在函数中创建,也可以在一个代码块中创建
let和const声明的变量不会有变量提升,也不可以重复声明
再循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制再循环内部
4)作用域链
在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象
1.块级作用域:let和const具有块级作用域,var不存在块级作用域,块级作用域解决了ES5的两个问题:分别是内层变量可以覆盖外层变量和用来计数的循环变量泄露为全局变量
2.变量提升:var存在变量提升,let和const不存在变量提升
3.给全局添加新属性:浏览器的全局对象是window,node的全局对象是global,var声明的变量为全局变量,并且会并且会将该变量添加为全局对象的属性,但是let和const不会
4.重复声明:var声明变量时,可以重复声明,后声明的同名变量名会覆盖之前生命的变量,const和let不允许重复声明变量
5.暂时性死区:再使用let和const命令声明变量之前,该变量都是不可用的,var声明变量就不会存在暂时性死区
6.初始值设置:再变量生命的时候,var和let可以不用设置初始值,但是const必须设置初始值
7.指针指向:let和const时ES6新增的,let可以更改指针指向,const不允许改变指针指向
通过call调用数组的 slice 方法来实现转换:
Array.prototype.slice.call(arrayLike);
通过call调用数组的 splice 方法来实现转换:
Array.prototype.splice.call(arrayLike, 0);
通过apply调用数组的 concat 方法来实现转换:
Array.prototype.concat.apply([], arrayLike);
通过 Array.from 方法来实现转换:
Array.from(arrayLike);
数组和字符串方法:toString(),toLocalString(),join()
数组增删(前增后增,前删后删)的方法:pop() 和 push(),shift() 和 unshift()
数组排序方法:reverse() 和 sort()
数组连接方法:concat() 不影响原数组
数组截取方法:slice() 不影响原数组
数组插入方法:splice(),影响原数组
数组通过索引查找方法:indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach()
数组归并方法:reduce() 和 reduceRight() 方法
数组判断是否存在值:includes()
typeof:其中数组,对下个,null都会返回object类型
instanceof:只能正确判断引用数据类型,不能精准判断基本数据类型,其中运行机制是判断在原型链中是否能找到改类型的原型,所以instanceof可以用来测试一个对象在原型链中是否存在一个构造函数的prototype属性
constructor:有两个作用,一个是判断数据类型,第二个是对象实例通过constructor对象访问的构造函数
Object.prototype.toString.call():通过js原型链去判断数据类型
通过 Object.prototype.toString.call() 判断
通过ES6中的 Array.isArray() 判断
通过 instanceof 判断
通过 Array.prototype.isPrototypeOf(obj) 判断