1 闭包
- 声明局部变量,防止污染全局
- 声明的变量能缓存(不销毁)
- 函数镶嵌,返回值是内层函数
- 内存泄漏 – 占内存
- 垃圾回收 – js自动垃圾回收,用完就销毁 ,栈内存,如果被引用或者标记就不会清除
5.1 —标记清理 非ie
垃圾回收程序运行→标记内存中所有变量→标记上下文中的变量和被引用的变量→删除没有标记的变量,释放内存。 (js最常用的垃圾回收策略)
5.2 —引用计数 ie
记录值被引用的次数,声明变量并赋值时,这个值的引用数为1,如果同一个值又被赋给另一个变量,那么引用数+1。如果引用该值的变量被覆盖,引用数-1。垃圾回收程序运行时会释放引用数为0的值的内存。
- 使用闭包的注意事项
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。(变量=null)
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
释放内存:
function fun(){
var i=0;
return function(){
alert(i++);
}
}
fun();//返回一个函数
fun()();//弹出0
var f1=fun();
f1();//弹出0
f1 = null // 释放内存
-
为什么要用闭包?
- 局部变量无法共享和长久的保存,而全局变量可能造成变量污染。我们希望有一种机制既可以长久的保存变量又不会造成全局污染。所以我们就用到了闭包
- 它的数据能缓存,不销毁。因为函数在全局被调用,所以会随函数一同保存到栈中,这样下一次使用的时候就可以拿到上一次的值。
-
垃圾回收机制
- js会自动垃圾回收,用完即销毁,但是栈内存不一样,栈内存的地址一直被引用(或者没被引用,被标记了),就不会销毁。
闭包样式:(了解即可)
function fn(){
var n =10
return function(){
return n
}
}
经典面试题:
下方代码输出结果为多少 为什么 如何依次输出0~4
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i, 'i') // 每隔1s输出一次 结果为5 5 5 5 5
}, i * 1000)
}
闭包:(立即执行函数)
for (var i = 0; i < 5; i++) {
(function (n) {
setTimeout(() => {
console.log(n, 'i') // 每隔1s输出一次 结果为0 1 2 3 4
}, n * 1000)
})(i)
}
也可使用 ES6 let声明解决(推荐)
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i, "i"); // 每隔1s输出一次 结果为0 1 2 3 4
}, i * 1000);
}
经典面试题:使用闭包实现:@3 防抖和节流
2 原型原型链
原型上的属性和方法实例可以共享,减少内存开辟
原型链 – 每一个对象都有原型,原型也是对象,也有它自己的原型,以此类推… ,一直找到最顶点Object.prototype上面,如果顶点没有的话就返回null
对象.__proto__ == 该对象的原型(prototype)
- 什么是原型
原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板
- 什么是原型链
对象之间的继承关系通过构造函数的prototype指向父类对象,直到指向Object对象为止形成的指向链条。
通俗讲: 原型链是原型对象创建过程的历史记录。
注:在javascript中,所有的对象都拥有一个__proto__属性指向该对象的原型(prototype) 。
- 原型上的状态和方法 不同实例之间可以共享
- 构造函数内的状态和方法,实例在使用时会新建
- 构造函数与原型内方法名称冲突时调用的是构造函数上的方法。
// 构造函数
function Person() {
this.fn1 = function () {
console.log("方法1_构造函数");
};
return this;
}
// 原型
Person.prototype = {
fn2: function () {
console.log("方法2_原型");
},
};
// 实例化
let res1 = new Person();
let res2 = new Person();
res1.fn1(); // 方法1_构造函数
res2.fn1(); // 方法1_构造函数
console.log(res1.fn1() == res2.fn1()); // true
console.log(res1.fn1 == res2.fn1); // false 同样是fn1方法 创建了两次(占两块内存)
res1.fn2(); // 方法2_原型
res2.fn2(); // 方法2_原型
console.log(res1.fn2() == res2.fn2()); // true
console.log(res1.fn2 == res2.fn2); // true 同样是fn2方法 共享(占同一块内存)
3 防抖和节流
定义:
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
使用环境:
1 防抖: 同一个事件在规定的时间内多次触发,会重新开始计时,直到规定时间结束,清除计时器,执行回调 ,用于减少事件触发频率。
export const debounce = (fn, awaits) => {
let timer;
return function () {
console.log("进入防抖");
let context = this;
let args = arguments;
if (timer) {
clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(function () {
fn.apply(context, args);
}, awaits);
}
};
};
防抖如果需要立即执行,可加入第三个参数用于判断,详情参考下方链接
2 节流: 规定时间内只运行一次,若在规定时间内重复触发,只有第一次生效,然后重新计时触发。
export const throttle = (func, wait) => {
console.log("进入节流")
let timeout;
return function () {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
4 数组的方法 和 遍历
1 forEach(()=>) 没有返回值 ,不能break, 不改变原数组,不能async/await
2 map(()=>) 有返回值 ,返回值为按回调函数映射的新数组 , 不改变原数组
3 for of 遍历数组
4 for in 遍历对象
5 filter(()=>)
6 some(()=>)
7 ervery(()=>)
8 reduce 求和
9 while 无限循环 找到跳出
10 find(()=>)
11 findIndex(()=>)
continue 跳转当前这次循环继续
break 破坏循环
retrun 函数
for in / for of 对象和数组的遍历:会就不用看
var arr = [1, 3, 5, 8, 7, 6, 0];
let obj = {
name: "swy",
age: 25,
hobby: "swimming",
};
for (var item in arr) {
console.log(item, "---", "for in arr"); // 下标 0~5 --- for in arr
}
for (var item of arr) {
console.log(item, "---", "for of arr"); // 值 1~0 '---' 'for of arr'
}
for (var item in obj) {
console.log(item, "---", "for in obj"); // key name~hobby --- for in obj
}
for (var item of obj) {
console.log(item, "---", "for of obj"); // 报错 Uncaught TypeError:obj is not iterable at 26行
}
改变原数组的方法:
shift() 首位删除,返回第一个元素
unshift() 首位增加,返回新的长度
pop() 末尾删除,返回最后一个元素
push() 末尾添加,返回长度
reverse() 反转数组
sort() 排序,按照首字母字符编码顺序排,要想按数字大小排需要使用排序函数
splice() 数组更新,返回 含有被删除的元素的数组,若没有删除元素则返回一个空数组
不改变原数组的方法:
concat() 拼接数组
join() 转字符串,默认“,”隔开
slice() 数组截取,返回新的数组
filter() ES5
map() ES5
forEach() ES5
some() ES5
every() ES5
reduce() ES5 **详见下方链接**
reduceRight() ES5 与reduce作用相同,遍历顺序相反
indexOf() ES5 返回要查找的项在数组中首次出现的位置,没找到返回-1
lastIndexOf() ES5 同上
includes() ES6 判断数组是否包含某个值,包含返回 true,否则false
find() ES6 遍历数组,返回符合条件的第一个值
findIndex () ES6 和find类似,默认返回的是索引,无符合条件元素返回 -1
Array.from() ES6 用于类似数组的对象(即有length属性的对象)和可遍历对象转为真正的数组
Array.of() ES6 将一组值转变为数组,参数不分类型,只分数量,数量为0返回空数组
这俩用不到:
flat()
flatMap()
reduce:reduce()详解
5 深拷贝和浅拷贝
使用场景
- 会经常改动一组数据,但原始数据还需要使用
- 我需要对数据操作前后进行对比
- 我需要两组一样的数据,但我不希望改动一个另一个随之改动
拷贝的划分都是针对引用类型来讨论的
基本数据类型拷贝:值保存在栈中,在对它进行拷贝时,其实是为新变量开辟新的空间
var str = 'How are you';
var newStr = str;
newStr = 10
console.log(str); // How are you
console.log(newStr); // 10
引用数据类型拷贝:拷贝了栈中数据的地址,所以更改其中一个数据另一个也会改变。
浅拷贝: 只拷贝引用数据类型的第一层数据,若第一层数据中还包含引用数据类型,则依然只拷贝该数据的引用地址。
所以如果数据嵌套层数较多(>=2),修改浅拷贝后里层的引用数据类型,依然会改变原数据。
实现方法:
var obj1 = {...obj}
或者
var obj1 = Obje.assign({},obj1,obj2,obj3)
深拷贝: 完完全全的拷贝存储一份新数据。
实现方法:
1、var obj1 = JSON.parse(JSON.stringify(obj))
不可使用这种方法,因为会丢失属性 undefined 和函数 xxx
2、用循环+递归的方法 来深拷贝
function deepClone(obj) {
if(typeof obj !=="object" || obj == null){
return obj
}
let res
if(obj instanceof Array){
res = []
}else{
res = {}
}
for(key in obj){
if(obj.hasOwnProperty(key)){
res[key] = deepClone(obj[key])
}
}
return res
}
// obj.hasOwnProperty('name') 判断name是不是obj对象的一个属性或者对象,返回true或false
// 此方法不能检测该对象的原型链中是否有该属性
6 es6新特
(1) let const
1 块级作用域 {}
2 不可以重复声明 (污染)
3 没有变量提升 先声明在调用 ()
4 const不可以修改值 -- 引用类型只要不修改地址就可以了
(2) 解构赋值
let [str,str1] = [0,'aaaa'] 就看位置结构
let {name} = {name:'zzzz'}
(3) … 拓展运算符
1 浅拷贝 ob1 = {...ob}
2 arr= [...arr,1] 添加属性
3 模拟arguments
function fn(...arg){
arg=[1,2,3,4]
}
fn(1,2,3,4)
(4) ${插值}
`${变量}`
`<div>${模版语法}</div>`
(5) Set Map
Set 去重复
[...new Set(arr)]
set.add()
set.has()
set.delete()
set.clear()
(6) Symbol
唯一,用来做属性的key {Symbol(name):"zzz",Symbol(name):"zhangsan"}
框架里面使用比较多 vue react
(7) class
封装接口可以使用class
react class组件
封装class
class A {}
class B extetnd A {
constructor(){
super()
}
aa(){}
}
class Qs {
static stringif(){
//code
}
static parse(){}
}
Qs.stringify() // 对象转化成转字 {name:'zzz',age:2} ===>. name=zzz&age=2
(8) for of
for(var item of arr){
console.log(item,'元素')
}
(9) includes
查看数组里面是否含有某个元素 ,以及字符串是否有某个字符串
(10) Object.keys,Object.values
提取对象的 key和value
(11) Proxy
vue3的响应式原理 vue2,3的原理
(12) Promise
7 函数 + this
普通函数
function fn(){
this 会随着调用改变而改变 其实就看.前面是谁就指向谁
}
通过 call , bind , apply 去修改this指向, 语法都是 fn.call(this指向) fn.apply(this指向) fn.bind(this指向)()
箭头函数
const fn=()=>{} (没有返回值) or fn=()=> 返回值
1 this指向不改变 创建时候是什么就是什么,不会改变了
2 没有arguments 但是可以通过 ... 模拟
3 不能够做构造函数 this不改变
4 call , bind , apply 不可以修改
8 Promise
1 解决回调地狱,使得原本通过回调函数嵌套回调函数才能得倒想结果,可以通过.then().then().then()清晰的通过链式语法得到,因为.then.then是按照顺序执行的,说白了在js开发过程中,由于是js是单线程异步语言,它不是按照谁先执行谁先结束所以需要按照同步的顺序执行,promise就解决了这个问题
2 Promise()
Promise.resolve() // .then
Promise.reject() // .catch
Promise.all([]) // 数组队列里面都执行完这之后 会执行.then()
Promise.race([]) // 只要有一个执行完就会执行.then()
3 Promise构造函数。new Promsie((resolve,reject)=>{ })
resolve(data) 执行 .then()
reject(err) 执行 .catch()
4 状态不可更改 , pending , fulfilled ,rejected
pending --> fulfilled 成功 reslove()
pending ---> rejected 失败 reject()
async function fn(){} Promise对象
async await 一起使用
封装 ajax
异步的东西都可以通过 promise
小程序 Promise分装 request
9 事件委托 + 事件流
1 事件委托(事件代理) 通过事件冒泡,将事件注册在他们的父级上,触发时候的时候通过事件对象,event.target来寻找事件源头,从而知道哪个元素触发的,相应处理
2 减少事件注册 动态添加的元素依然可以使用该事件
3 事件分为冒泡和捕获 通过dom2绑定事件 设置冒泡或者捕获 addEventListener("click",callback,false/true)
4 dom0 onclick onmouseve 不能在同一个元素重复绑定事件
10 cookie + Storageg
1 大小不一样 cookie 4kb Storage存储大5m
2 cooike存储的时候一定要有服务器环境 , storeage不需要
3 cookie一般都是有后端发起, 前端说设置携带cookie就可以将cookie带给后台
4 本地存储就是前端发起的存储,所以在使用由前端获取
5 cookie存储在顶级路径 http://127.0.0.1:8080 http://127.0.0.1:8080/list http://127.0.0.1:8080/a
6 本地存储 只要存储到 http://127.0.0.1:8080 就都可以获取到
7 cookie可以有效期。,本地存储不可以
8 自定义设置有效期 重点****
自己定义的方法
9 sessionStorage 和 localStorage