JS的基本概念
诞生
JS诞生于1995年,由Brendan Eich开发,主要借鉴了c语言的基本语法、Java语言的数据类型和内存管理,Scheme语言将函数提升到“第一等公民”的地位,借鉴Self语言,使用基于原型的继承机制。
数据类型:
基本数据类型
- Number
- String
- Boolean
- Null
- Undefined
- Symbol(ES6新增数据类型)
- bigInt
引用数据类型统称为Object类型,细分的话有
- Object
- Array
- Date
- Function
- RegExp
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。
顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗。
变量提升
对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值。
- var有变量提升,const、let没有变量提升,提前访问会报错;
- function函数可以先调用再定义;
- 赋值给变量的函数无法提前调用;
- 对变量进行提升,只声明,不赋值,值为undefined;
JS的执行机制
同步: 前一个任务执行完之后再执行另一个任务,执行顺序就是排列顺序
异步: 在做一个任务的同时做另一个任务 同步任务(web API)都在主线上执行,形成一个执行线,异步任务是通过回调函数实现的,只要有三种类型:普通事件(click、resize等)、资源加载事件(load、error等)、定时器(sentInterval、setTimeout等) 异步任务相关回调函数添加到任务队列中(消息队列)
S执行机制:
- 先执行主执行线上的任务,遇到回调函数,则把回调函数放入到任务队列中,不执行,接着执行主执行线上的其他任务
- 主执行线上的所有同步任务执行完毕之后,系统会依次读取任务队列中的异步任务,于是异步任务进入主执行线,开始执行。
- 将回调函数放入到任务队列的过程中,会进行异步进程处理,就是如果是一个点击事件,如果没有点击在,则异步进程处理不会将这个点击事件的回调函数放入到任务队列
例如如果是定时器,需要等待设定的定时时间才会放入到任务队列中。 由于主执行线(主线程)不断地重复获取任务、执行任务、再获取任务。再执行,所以这种机制被称为事件循环(eventLoop)
创建执行上下文的时候做了什么?
- 绑定This;
- 创建此法环境;
- 创建变量环境;
宏任务、微任务:
先同步再异步,执行同步任务过程中,遇到微任务就放到微任务队列,宏任务放到宏任务队列; 执行完同步任务,将微任务调入主线程执行,清空队列; 再从宏任务队列中第一个宏任务出来,又分为同步任务、微任务、宏任务,按顺序执行; 直到最后一个宏任务执行完毕。
宏任务: script、setTimeOut、setInterval、setImmediate
微任务: promise.then、process.nextTick、Object.observe、MutationObserver
同步任务: console那些从上到下执行的,new Promise
JS的进阶知识点
垃圾回收机制
首先先理解什么是内存泄漏?
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏。
为什么会导致内存泄漏?
内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃。
垃圾回收机制有哪些策略?
标记清除法
垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除。
引用计数法
当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象 缺点: 当两个对象循环引用的时候,引用计数无计可施。如果循环引用多次执行的话,会造成崩溃等问题。所以后来被标记清除法取代。
浅拷贝、深拷贝
浅拷贝: 将原对象/数组的引用直接赋给新对象/数组,对基本对象类型的值的拷贝,对引用类型的地址的拷贝,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化
深拷贝: 创建一个新对象/数组,将原对象/数组的值复制过来,两个对象修改其中任意的值另一个值不会改变。在另一个对象中开辟对应的空间,空间大小占用一样但是位置不同
1、浅拷贝
第一层原对象改变,不会改变浅拷贝后的对象; 第二层原对象改变,会改变浅拷贝后的对象
对象:
-
for in 循环遍历复制
function shallowClone1(o) { let obj = {} for (let i in o) { obj[i] = o[i] } return obj } -
三点运算符
var shallowObj2 = { ...obj1 }
-
assign()方法
var shallowObj3 = Object.assign({}, obj1)
数组:
-
Array.prototype.slice(0),
-
Array.prototype.concat() 2、深拷贝
-
for循环
function deepClone(o) { //简易版,没有考虑到传入的参数类型 let obj = {} for (var i in o) { if(o.hasOwnproperty(i)){ if (typeof o[i] === "object") { obj[i] = deepClone(o[i]) } else { obj[i] = o[i] } } } return obj } //升级版1,考虑了对象、数组类型 function isObject(o) { return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]" } function deepClone(o) { if (isObject(o)) { let obj = Array.isArray(0) ? [] : {}; for (let i in o) { if (isObject(o[i])) { obj[i] = deepClone(o[i]) } else obj[i] = o[i] } return obj } else return o; } -
JSON.stringify()
function cloneJson(o) {
return JSON.parse(JSON.stringify(o))
}
防抖、节流
1、防抖
- n秒后执行该事件,若在n秒内被重复触发,则重新计时
- 应用:多次点击,造成多次提交,每次点击前都重新计时
let timer; //闭包,因为每次都不需要重新定义延时时间,只需要初始化一次,且每次调用只是不断赋值
return function (){
let context = this; //改变this指向
let args = arguments; //增加参数给func使用
clearTimeout(timer); //重新计时
timer = setTimeout(() => {
func.apply(context,args) //修改this指向触发事件的DOM,原先是window
}, delay);
}
}
btn.addEventListener('click',debounce(fun,1000))
2、节流
设置时间间隔,在时间间隔内,再次触发的事件会被忽略
let timer;
return function() {
if (timer) return;
let context = this;
let args = arguments;
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, delay)
}
}