作用域
作用域也叫执行上下文,规定了执行代码对变量的访问权限。
全局作用域和函数作用域
- 全局作用域
- 最外层函数和最外层函数外面定义的变量拥有全局作用域。
- 所有未定义直接赋值的变量自动声明为全局作用域。
- 所有window对象的属性拥有全局作用域。
- 函数作用域
- 声明在函数内部的变量拥有函数作用域
块级作用域
let,const声明的变量具有块级作用域,块级作用域可以在函数中创建也可以在代码块中创建。
var,let,const的区别
- var存在变量提升,声明的变量会挂载在windows上,可以重复声明。
- let,const具有块级作用域、不存在变量提升、暂时性死区、不能重复声明
注意:const声明之后必须马上赋值,否则会报错;const声明的基本类型不能更改,声明的复杂类型不能更改内存地址,可以更改内部属性。
在当前作用域中查找变量时,没有找到就会从父级作用域查找,还没有找到就依次向上查找,查找的最后一个对象是全局对象。查找的这条链就叫作用域链。
闭包
一个函数能够访问另一个函数内部的变量的时候,就形成闭包
为什么会内存泄漏?
因为当外层函数执行完毕后,外层函数执行环境的作用域链会销毁,但此时内部函数还在引用外层函数的活动对象,这个活动对象会留在内存中。 解决办法:执行外层函数的时候会分配一块堆内存,把堆内存中的值置为null就行了
内存泄漏的场景:
- 意外的全局变量 使用了未声明的变量,变量就会变成全局变量,那这个变量就会一直保存在内存中无法被回收
- 定时器:如果定时任务不被使用了,但是没有清除,那这个定时器所引用的外部变量就会一直保持在内存中无法被回收。解决办法:
不使用定时器时及时清除定时任务 - 闭包:外部函数执行时会分配一块堆内存,外部函数执行完之后如果没有对堆内存置空,那内部函数就会仍然引用外部函数的变量,导致变量无法被回收。解决办法:
外部函数执行完之后将对应的堆内存置空 - dom的引用:在引用一个dom元素之后,删除这个dom元素,此时dom元素已经不存在了,但是这个元素的引用还存在,所以会造成内存泄漏。解决办法:
应该在移除之后将引用清空。 使用场景
1.回调函数
function add(num1,num2,callback){
let sum = num1+num2;
callback(sum)
}
add(1,2,function(sum){
console.log(sum)
})
2.防抖节流
防抖:事件触发n秒后再执行回调,如果n秒内被再次触发,则重新及计时,比如浏览器上的模糊搜索
function debounce(fn, delay) {
let timer = null;
return function() {
if(timer !== null) {
clearTimeout(timer) //如果计时任务已经存在,并且又触发了相同的事件,则取消当前计时,重新计时
}
timer = setTimeout(
() => fn.call(this), delay)
}
}
节流:事件连续触发,但n秒内只执行一次回调,比如:scroll,resize事件
function throttle(fn, delay) {
let flag = true;
return function() {
if(flag) {
setTimeout(() => {
fn.call(this);
flag = true;
}, delay)
}
flag = false
}
}
apply的传参只能是一个数组;call的传参可以传多个;bind用来创建一个函数,也可以传参
- 柯里化函数
function uri(protocol) {
return function (hostname, pathname) {
return `${prootocol}${hostname}${pathname}`
}
}
uri('http://')('xxx', 'yyy')
Promise
什么是promise?
promise是一种异步编程解决方案,它是一个对象,可以从中获取异步操作的消息。用.then的链式调用解决了多层异步操作时的回调地狱问题
promise有三种状态:pending,resolved,rejected,promise必然处于其中之一,状态一旦发生改变就不会再变,状态的改变是通过resolve()和reject()方法来实现的。
promise实例方法有catch,then,finally
Promise的静态方法有all,race,resolve,reject等,all比较常用,通常用来处理没有先后顺序的请求。
- .all中只要有一个promise返回失败就会失败
- .race含有竞速的意思,将多个Promise放在一个数组中,数组中第一个得到结果的promise,不管是" 完成
- (resolved)"还是" 失败(resolved)" , 那么这个 .race 方法就会返回这个结果。
- .resolve 返回一个resolved状态的Promise实例
- .reject返回一个rejected状态的Promise实例
解决了什么问题?
- 回调地狱:比如ajax请求有前后依赖关系时,那就需要嵌套ajax请求,多了的话就会嵌套很多层,也就是回调地狱promise的作用就是解决ajax的回调地狱问题,将嵌套请求变成链式调用
- 可支持并发请求,获取并发请求的结果
async/await
async/await其实是一个语法糖,返回的是一个promise,他能实现的promise都能实现,相当于promise的一个优化。
- async代码更清晰易于阅读,Promise的链式调用长了就会造成阅读负担。
- async几乎是同步的写法,非常优雅,promise传递中间值非常麻烦
- async调试更友好。promise中,由于没有代码块,你不能在一个返回表达式的箭头函数中设置断点。如果你在.then代码块中设置断点,调试器不能进入下一个.then。
js中有哪些数据类型?有什么区别
基本数据类型: undefined、null、string、boolean、number、symbol
symbol代表独一无二的数据类型,主要是为了解决可能出现的全局变量冲突的问题
原始数据类型存储在栈中,大小固定,占用空间小
复杂数据类型: 对象、数组、函数
复杂数据类型存储在堆中,引用类型的变量其实是一个指针,指向的是堆内存中的地址。
typeof和instanceof的区别
typeof判断数组|对象|null结果都是object.判断其余基本数据类型和function都能正确判断
instanceof能够正确判断引用数据类型。instanceof的运行机制是在数据的原型链中能否找到该类型的原型。
Object处理了一些特殊情况,比如
== 和 === Object.is的区别
===:严格相等,比较类型和值 ==:抽象相等,先对左右两边的值进行类型转换再比较值 Object.is 一般和===的判断结果相同,它处理了一些特殊情况,比如:
Object.is(-0,+0) //false
Object.is(NaN, NaN) //true
undefined == null //true
undefined === null //false
浅拷贝和深拷贝
引用类型的变量其实是一个指针,这个指针指向的是堆内存中的地址。
浅拷贝新旧对象还是共享同一块内存,但深拷贝是为新对象开辟了一个新的内存空间。
- 直接赋值 引用类型直接复制其实是不同的指针指向了相同的引用对象,所以会相互影响。
- 浅拷贝 浅拷贝是创建一个新的对象,浅拷贝只是复制了第一层对象的属性,因此如果对象内的属性是引用类型,修改是会相互影响的。但第一层数据类型为基本类型时,浅拷贝后不会相互影响。
浅拷贝的方式:
数组:Array.concat() Array.slice()
对象:Object.assign({}, obj)
复制代码
- 深拷贝 递归拷贝对象中的每一个属性,所以深拷贝后两个对象之间互不影响。
深拷贝的方式:
JSON.parse(JSON.stringify(obj)) //缺点:对象中包括日期或者正则表达式时,会跟源数据不一致
function deepClone(source) {
if(source ===null) return source;
if(source instanceof Date) return new Date();
if(source instanceof RegExp) return RegExp;
if(typeof source !== 'object') return source
let obj = Array.isArray(source) ? [] : {};
for(let i in source) {
if(source.hasOwnProperty(i)) {
obj[i] = deepClone(source[i])
}
}
return obj;
}
lodash中的cloneDeep
原型和原型链
什么是原型?
原型分为隐式原型和显示原型。每个引用类型的数据都有一个隐式原型__proto__,这个值指向它的构造函数的显式原型prototype, 这两个值完全相等。
什么是原型链?
当查找对象中的属性或者方法时,首先会从自身查找,如果没有查找到,就会去它的原型上去找,如果再找不到,就会去原型的原型上去找,直到null为止。查找的这条链就叫原型链。
原型链是一个过程,原型则是这个过程中的单位,贯穿整个原型链。
js运行机制
js是单线程的,也就是所有的任务都需要排队,如果前面的任务没有执行完,那么后面的任务就需要一直等,这样用户体验很差,所以就有了异步的概念。
同步任务:在主线程排队的任务
异步任务:不进入主线程,会被放进任务队列。分为宏任务和微任务
```
宏任务:script,setTimeout,setInterval
复制代码
```
```
微任务:promise.then,nextTick,async await (需要耗时的一些任务)
复制代码
```
同步任务依次执行完之后,会从任务队列中拿到宏任务来执行,宏任务执行完后会执行该任务中的微任务,以此循环。即事件循环。
计时器为什么不准?计时器不是到点就执行,而是到点放到任务队列中,轮到它了再去执行
事件冒泡和捕获
事件冒泡:事件在最内层元素发生,并且向上传播。
事件捕获:事件在最外层元素发生,直到最具体的元素。
element.addEventListener(event, fn, useCapture) //useCapature为true时表示在捕获阶段调用处理函数,为false表示在冒泡阶段调用处理函数。
场景: 在点击li标签时,需要执行某些操作,常规做法是去遍历li标签绑定事件,那当li标签特别多的时候,很消耗性能。如果利用事件冒泡,那此时只需要在ul元素上去监听点击事件,事件在li元素上发生,会传播到ul元素,此时会触发ul的点击事件,再利用target去判断事件的发生地,就可以去执行操作了
继承
- 原型链继承
function A(name, age) {
this.name = name;
this.age = age;
}
A.prototype.getAge = (name) => name
function B(){}
B.prototype = new A()
复制代码
缺点:原型链上的修改都会变成通用的
- 构造函数继承
function A(name, age) {
this.name = name;
this.age = age;
}
A.prototype.getAge = (name) => name
function B(name){
A.call(this,name)
}
const b = new B('aa')
复制代码
缺点:不能继承原型链上的方法
- 组合继承(原型+构造函数)
function A(name, age) {
this.name = name;
this.age = age;
}
A.prototype.getAge = (name) => name
function B(name){
A.call(this,name)
}
B.prototype = new A('hh')
复制代码
缺点:初始化了两次
- 寄生组合继承
function A(name, age) {
this.name = name;
this.age = age;
}
A.prototype.getAge = (name) => name
function B(name){
A.call(this,name)
}
B.prototype = Object.create(A.prototype)
get和post请求的区别
| 类别 | get请求 | post请求 | 备注 |
|---|---|---|---|
| 用途 | 用来获取资源 | 用来创建或者获取资源 | |
| 参数位置 | get的参数会携带在url上 | Post的参数在请求体中 | |
| 安全性 | get请求信息在url上,不安全 | post比较安全 | |
| 参数大小 | url中参数最大为2048个字符 | Post的参数大小没有限制 | |
| 数据包 | get请求只会产生一个tcp数据包 | post请求会产生两个tcp数据包 | 1、对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);2、而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。 |
| 浏览器回退 | get请求在浏览器回退时不会重新发起请求 | post请求会 |
Proxy
proxy是ES6中新增的功能,可以自定义对象中的操作。Vue3中使用Proxy代替Object.defineProperty来实现数据响应式。
箭头函数和普通函数的区别
- 箭头函数写法更简洁
- 箭头函数没有自己的this,通过上下文确定自己的this
- 箭头函数没有自己的原型
- 箭头函数不能作为构造函数使用
- 函数体内没有arguments对象
es6新特性
- let, const
- 数组
- 扩展运算符
- Array.from(),Array.of()
- find(),findIndex()
- fill(value, startIndex, endIndex)
- includes()
- flat()
- entries(),keys(),values()
- 对象
- Object.is(),Object.assign()
- Object.keys(),values(),entries()
- promise
- 模块化(import,export)
数组方法
- map
- forEach
- push
- pop
- shift
- unshift
- filter
- reduce
- concat
- slice
- splice
- reverse
- sort
- some, every
字符串方法
trim,concat,slice,substring,includes,chartAt,split,match,toUpperCase,toLowerCase
reduce方法
reduce(function, 初始值)