什么是闭包
- 什么是闭包?
- 闭包是JS的语法特性,如果一个函数访问了它外面的自由变量,那么这个函数和自由变量就构成了闭包。自由变量是指既不是全局变量,也不是函数局部变量的变量。
- 举例:声明一个立即执行函数,立即执行函数里面声明一个变量,然后再声明一个函数对这个自由变量进行操作,就形成了闭包
!function f(){
var count
return function(){
count += 1
}
}()
- 闭包的用途
- 提供接口,间接访问局部变量
- 避免污染全局变量
- 能够维持变量,不被垃圾回收
- 闭包的缺点
- 使用不当可能造成内存泄漏
- 旧版IE对闭包支持不好
call、apply、bind 的用法分别是什么?
call
call() 使用 一个指定的this值和一个或多个参数 调用一个函数
// 语法
func.call(thisArg,arg1,arg2...)
// 应用:实现 forEach
Array.prototype.forEach2 = function (fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i], i, this)
}
}
let array = [2, 9, 5, 1]
array.forEach2.call(array, (item) => console.log(item))
apply
apply()方法和call类似,也是显式指定this指向,区别是接收数组或类数组作为函数执行时的参数
// 语法
func.apply(thisArg,argsArray)
// 应用1:将类数组转成数组
Array.prototype.slice.apply({0:1,length:1}) // [1]
// 应用2:求数组最大值
Math.max.apply(null,[5,6,2,3,7])
bind
bind()方法创建一个新的函数,将函数体内的this绑定到某个对象
// 语法
func.bind(thisArg[, arg1[, arg2[, ...]]])
// 应用:让this不被改变
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name:'amber'}) // f2 就是 f1 绑定了 this 之后的新函数
f2() // 等价于 f1.call({name:'amber'})
let f3 = f1.bind({name:'amber'}, 'hi') // 还可以绑定其它参数
f3() // 等价于 f1.call({name:'amber'}, hi)
请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。
1xx 信息性状态码
101 Switching Protocol 服务端同意客户端升级协议的请求(Upgrade请求头),切换协议
2xx 成功状态码
200 OK表示请求成功
204 No Content 表示请求成功,但服务端没有返回的内容;一般用于客户端往服务器端发送PUT请求,而服务器端不需要往客户端发送内容
206 Partial Content 客户端进行了范围请求(Range请求头),服务端执行了这部分请求,响应报文包含Content-Range指定范围的内容
3xx 重定向状态码
301 Moved Permanently 永久重定向,请求资源已经移动到响应头Location指定的URL上,不会再改变
302 Found 临时重定向,请求资源暂时移动
304 Not Modified 与内容协商相关,客户端发送带条件的请求(请求头有If-None-Match或If-Modified-Since)时,告诉客户端可以继续使用缓存的内容
4xx 客户端状态码
404 Not Found 服务端无法找到请求的资源
405 Method Not Allowed 服务器不支持当前 HTTP 方法的请求
406 Not Acceptable 资源无法满足客户端的条件
409 Conflict 多个请求发生了冲突
413 Request Entity Too Large 请求体的数据过大
429 Too Many Request 客户端发送的请求过多
5xx 服务端状态码
503 服务器暂时处于超负载或停机维护,无法处理请求
数组去重
使用Set
缺点 太简单
const unique = (arr) => {
return Array.from(new Set(arr)) // [...new Set(arr)]
}
const unique = (arr) => {
return [...new Set(arr)]
}
不使用Set
Map
const uniq = (arr) => {
let map = new Map()
for (let i = 0; i < arr.length; i++) {
let item = arr[i]
if (item === undefined) {
continue
} // 考虑稀疏数组
if (map.has(item)) {
continue
}
map.set(item, 'true')
}
return [...map.keys()]
}
// filter简化
const uniq = (arr) => {
let map = new Map()
let res = arr.filter((item) => {
return !map.has(item) && map.set(item, 1)
})
return res
}
filter + indexOf
const uniq = (arr) => {
let res = arr.filter((item, index, array) => {
return array.indexOf(item) === index
})
return res
}
for循环+indexOf
function uniq2(arr) {
var res = []
for (let i = 0; i < arr.length; i++) {
let current = arr[i]
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res
}
双层for循环
// 双层for循环,外层遍历需要去重的数组,内层遍历新数组
// 在内层循环中判断,如果值相等,就跳出循环
// 如果都不相等,j的值就会等于newArr的长度,根据这个特点把值添加到新数组
function uniq1(arr) {
var newArr = []
for (var i = 0, len = arr.length; i < len; i++) {
for (var j = 0, newLen = newArr.length; j < newLen; j++) {
if (arr[i] === newArr[j]) {
break
}
}
if (j === newLen) {
newArr.push(arr[i])
}
}
return newArr
}
DOM相关
-
什么是事件委托?
在需要绑定大量事件时可以使用事件委托。
事件委托利用了事件冒泡的机制,在上层元素上添加事件监听函数,来管理其子孙元素上的同一类事件。
具体实现:给祖先元素绑定监听函数,函数内部检查事件源event.target,如果事件发生在我们感兴趣的元素上,就处理该事件
优点:减少监听器数量,节省内存占用;可以监听动态元素
缺点:在调试的时候不容易确定监听者
-
怎么阻止默认动作?
e.preventDefault()或return false -
怎么阻止事件冒泡?
e.stopPropagation()
JS继承
原型链继承
function Parent(name) {
this.name = name
this.colors = ["red", "blue", "yellow"]
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name) // 继承父类属性
this.age = age;
}
Child.prototype = new Parent(); // 子类原型为父类的实例对象
// 也可以写成
// Child.prototype.__proto__ = Parent.prototype
Child.prototype.sayAge = function () {
console.log(this.age);
}
let child1 = new Child("amber", 19);
child1.colors.push("pink");
console.log(child1.colors); // ["red", "blue", "yellow", "pink"]
child1.sayAge(); // 19
child1.sayName(); // "amber"
let child2 = new Child("tom", 22);
console.log(child2.colors); // ["red", "blue", "yellow"]
child2.sayAge(); // 22
child2.sayName(); // "tom"
class继承
通过extends关键字实现继承
class Person{
constructor(name,age){
this.name = name
this.age = age
}
showName(){
console.log(this.name,this.age);
}
}
class Child extends Person{
constructor(name,age,gender){
super(name,age)//通过super调用父类的构造方法
this.gender = gender
}
showName(){
console.log(this.name,this.age,this.gender);
}
}
let child1 = new Child('amber',24,'female')
console.log(child1) // {name: 'amber', age: 24, gender: 'female'}
child1.showName() // amber 24 female
数组排序
选择排序
每一次找最小数放前面,然后对后面的数做同样的事情
let sort = (arr) => {
for (let i = 0; i < arr.length - 1; i++) {
let index = minIndex(arr.slice(i)) + i
// index对应的数应该放到i处
if (index !== i) {
swap(arr, index, i)
}
}
}
let swap = (arr, i, j) => {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
let minIndex = (arr) => {
let index = 0
for (let i = 1; i < arr.length; i++) {
if (arr[index] > arr[i]) {
index = i
}
}
return index
}
sort(array)
快速排序
指定一个基准数,比它小的放左边,比它大的放右边
let quickSort = (arr) => {
// 终止条件
if (arr.length <= 1) return arr
// 指定一个基准
let index = Math.floor(arr.length / 2)
// 数组排除基准数
let num = arr.splice(index, 1)[0]
let left = []
let right = []
// 剩余项和基准数比较,小的放左边,大的放右边
for (let i = 0; i < arr.length; i++) {
if (arr[i] < num) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([num], quickSort(right))
}
归并排序
左边一半排好序,右边一半排好序,最后合并起来
let mergeSort = (arr) => {
// 把数组拆分到一个数组只有一项(已排序),再合并
let k = arr.length
// 递进的终止条件
if (k === 1) {
return arr
}
let left = arr.slice(0, Math.floor(k / 2))
let right = arr.slice(Math.floor(k / 2))
return merge(mergeSort(left), mergeSort(right))
}
// 核心 合并
let merge = (arrA, arrB) => {
if (arrA.length === 0) return arrB
if (arrB.length === 0) return arrA
return arrA[0] < arrB[0]
? [arrA[0]].concat(merge(arrA.slice(1), arrB))
: [arrB[0]].concat(merge(arrA, arrB.slice(1)))
}
计数排序
利用哈希表
let countSort = (arr) => {
let hashTable = {}
let max = 0
let res = []
for (let i = 0; i < arr.length; i++) {
if (!(arr[i] in hashTable)) {
hashTable[arr[i]] = 1
} else {
hashTable[arr[i]] += 1
}
if (max < arr[i]) max = arr[i]
}
for (let j = 0; j <= max; j++) {
if (j in hashTable) {
for (let k = 0; k < hashTable[j]; k++) {
res.push(j)
}
}
}
return res
}
冒泡排序
每次循环依次比较两个数
function sort(arr) {
const len = arr.length
// 外层循环i控制比较的轮数
for (let i = 0; i < len; i++) {
// 里层循环控制每一轮比较的次数j,arr[i] 只用跟其余的len - i个元素比较
for (let j = 1; j < len - i; j++) {
// 若前一个元素"大于"后一个元素,则两者交换位置
if (arr[j - 1] > arr[j]) {
let temp = arr[j-1]
arr[j-1]=arr[j]
arr[j]=temp
}
}
}
return arr
}
Promise
Promise是异步编程的解决方案,能够将异步任务以同步操作的流程表达出来,避免回调地狱
创建一个Promise实例
const p = new Promise((resolve,reject)=>{
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别为resolve和reject函数
resolve函数的作用是将Promise实例状态从pending变为resolved,在异步任务成功时调用,将异步任务的结果作为参数传递出去
reject函数的作用是将Promise实例状态由pending变为rejected,在异步任务失败时执行,将错误作为参数传递出去
Promise.prototype.then()
可以用then方法指定Promise实例状态改变时的回调函数
p.then((value) => {
// success
}, (error) => {
// failure
})
then()方法会返回一个新的promise对象,实现链式调用
以封装ajax请求为例
const ajax = (method,url)=>{
return new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest()
xhr.open(method,url)
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status <300){
resolve(xhr.response) // 请求成功
}else{
reject(new Error(xhr.statusText)) //请求失败
}
}
}
xhr.setRequestHeader("Accept", "application/json");
xhr.send()
})
}
// 第一个then返回一个值
ajax('get','/posts.json').then((json)=>{
return json.post
},(error)=>{
console.log('失败:'+error)
}).then((post)=>{ // 上一个回调函数返回结果作为参数,传入第二个回调函数
//...
})
// 第一个then也有可能返回一个Promise对象
ajax('get','/posts.json').then(
json => ajax('get',json.commentURL)
}).then(
comments => console.log("resolved: ", comments), // 当第一个then方法指定的回调函数返回的Promise对象状态变为resolved,调用这个回调函数
err => console.log("rejected: ", err)
});
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
依旧以封装ajax请求为例
// 生成一个Promise对象的数组,请求多个json文件
const promises = [2, 3, 5, 7, 11, 13].map((id) => {
return ajax('get',`/post/ + ${id} + .json`);
});
Promise.all(promises).then((posts) => {
// 所有文件都请求成功才会执行这个回调
}).catch((reason) => {
// ...
});
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
下面代码中,如果5秒之内fetch请求没有返回结果,p的状态就会变为rejected,触发catch方法指定的回调
const p = Promise.race([
fetch(url,options),
new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error('request timeout')),5000)
})
])
p
.then(console.log)
.catch(console.error);
两种方法等价
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
说说跨域
-
什么是同源 浏览器为了保护用户隐私和数据安全制定了同源策略,同源策略规定:如果两个URL的协议、域名和端口号都一致,这两个URL就是同源的
-
什么是跨域 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。请求可以发出,但是响应会被浏览器阻止。
-
JSONP 跨域 核心思想:利用script标签可以跨域的特性。
如何实现:
-
站点A声明一个回调函数,函数参数是想要获取的数据,创建script标签,src是跨域请求的url,并且将函数名以问号传参的方式发送给站点B;
script.src = `${url}?callback=${funcName}` -
站点B收到请求后,后端改造JS文件内容,将函数名和数据拼接成字符串返回给站点A
-
站点A这边接收到响应,执行回调函数,就可以拿到数据
缺点:由于是script标签,只能发get请求,不支持post请求
优点:兼容性好
-
-
CORS 跨域 a. 对于简单请求,浏览器发送的请求报文包含Origin字段,被请求站点在响应头添加
Access-Control-Allow-Origin: http://请求站点// 请求报文 GET /resources/public-data/ HTTP/1.1 ...省略 Origin: https://foo.example // 响应报文 Access-Control-Allow-Origin: https://foo.exampleb. 对于复杂请求,浏览器会先使用OPTIONS方法发起一次预检请求,服务器需要设置如下响应头
// 响应报文 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type预检请求完成后,再发送实际请求,服务器设置响应头
Access-Control-Allow-Origin: http://请求站点c. 对于携带身份凭证的请求,前端在ajax请求时要设置
xhr.withCredentials = true字段,服务端响应头需设置response.setHeader("Access-Control-Allow-Credentials", "true");,启用此项后,允许跨域访问的域名Access-Control-Allow-Origin不能为*