1.闭包
1.1什么是闭包?
一个函数和它使用的函数外部的变量就组成了闭包
举例:
function f1(){
let a=1
function f2(){
let a=2
function f3(){
console.log(a)
}
a=22
f3()
}
f2()
}
f1()
1.2.闭包的用途是什么?
-
闭包可以让你从内部函数访问外部函数作用域
-
能够维持变量,不被垃圾回收
1.3.闭包的缺点是什么?
常驻内存 会增大内存的使用量 使用不当会造成内存泄露
2.call、apply、bind 的用法分别是什么?
2.1call:
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
fn.call(xxx, 1, 2, 3)
第一个参数是this, 后面所有的参数是arguments
Array.prototype.forEach2 = function(fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i], i)
}
}
let arr = [1,2,3]
arr.forEach2.call(arr, (item)=>console.log(item))
2.2apply:
该方法的语法和作用与call方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
求数组里的的最大值和最小值:
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max);
// expected output: 7
const min = Math.min.apply(null, numbers);
console.log(min);
// expected output: 2
2.3bind:
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法:function.bind(thisArg[, arg1[, arg2[, ...]]])
3. 请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。
HTTP 状态码(英语:HTTP Status Code)是用以表示 HTTP 响应状态的 3 位数字代码
- 100~199 消息
- 200-299 成功
- 300 -399 重定向
- 400-499 客户端出错
- 500-599 服务器出错
100 ~ 199
服务器已经收到请求头,请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分
101:服务端已经理解了客户的请求,并通过 Ugrade 消息头通知客户端采用不同的协议来完成这个请求
200~299
200: 已成功处理了请求。出现此状态码是表示正常状态
201: 请求成功并且服务器创建了新的资源
202:服务器已接受请求,但尚未处理
204:服务器成功处理了请求,但没有返回内容
206:服务器成功处理了部分 GET 请求
300~399
300:针对请求,服务器可执行多种操作。服务器可根据请求者选择一项操作,或提供操作列表供请求者选择
301:请求的资源已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
302:请求的资源临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了指定的情况下,这个响应才是可缓存的。
303:对应当前请求的响应可以在另一个 URL 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的 POST 请求输出重定向到一个新的资源。这个新的 URI 不是原始资源的替代引用。同时,303响应禁止被缓存。当然,第二个请求(重定向)可能被缓存。
304:自从上次请求后,请求的资源未修改过。服务器返回此响应时,不会返回资源的内容,因此可节省带宽和开销。
400~499
400:服务器不理解请求的语法。
401:请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
403:服务器拒绝请求。
404:服务器找不到请求的资源。 例如,对于服务器上不存在的资源经常会返回此代码。
408:服务器等候请求时发生超时
409:由于和被请求的资源的当前状态之间存在冲突,请求无法完成。
429:用户在给定的时间内发送了太多的请求。旨在用于网络限速。
500~599
500:服务器遇到错误,无法完成请求
502:服务器作为网关或代理,从上游服务器收到无效响应。
503:服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
4.数组去重
假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
4.1使用set
const unique=Array.from(new Set(array));
缺点:他无法去重引用类型的数据,比如对象数组。
4.2不使用set
4.2.1双重for循环
arr = [1,5,2,3,4,2,3,1,3,4]
for(var i=0;i<arr.length;i++){
var cur=arr[i];
for(var j=i+1;j<arr.length;){
if(cur===arr[j]){
arr.splice(j,1)
}else{
j++
}
}
}
console.log(arr);
缺点:
比较麻烦 占内存
4.2.2reduce+indexOf
array = [1,5,2,3,4,2,3,1,3,4]
var unique = array.reduce(function (accumulator, currentValue) {
if (accumulator.indexOf(currentValue) === -1) {
accumulator.push(currentValue);
}
return accumulator
}, [])
console.log(unique);
4.2.3Map
arr = [1,5,2,3,4,2,3,1,3,4]
function arrayNoRepeat(arr) {
let map = new Map();
let result = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 判断 map 中是否已有该 key 值
map.set(arr[i], true); // 后面的true 代表该 key 值在原始数组中重复了,false反之
} else { // 如果 map 中没有该 key 值,添加
map.set(arr[i], false);
result.push(arr[i]);
}
}
return result;
}
console.log(arrayNoRepeat(arr));
4.2.4计数排序
unique = (array) => {
const hash = []
for(let i=0;i<array.length; i++){
hash[array[i]] = true
}
const result = []
for(let k in hash){
result.push(k)
}
return result
}
缺点:只支持数字或者字符串数组,如果数组里面有对象,比如 array = [{number:1}, 2],就会出错。
5.Dom事件相关
5.1什么是事件委托
由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数,
因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件
具体实现:给祖先元素绑定监听函数,函数内部检查事件源event.target,如果事件发生在我们感兴趣的元素上,就处理该事件
优点:减少监听器数量,节省内存占用;可以监听动态元素
缺点:在调试的时候不容易确定监听者
5.2怎么阻止默认动作?
e.preventDefault()
如果 Event 对象的 cancelable 属性是 fasle,那么就没有默认动作,或者不能阻止默认动作。无论哪种情况,调用该方法都没有作用。
5.3怎么阻止冒泡?
捕获不可以取消,但是冒泡可以取消,e.propagation()
6.JS继承
6.1基于原型的继承
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("lili", 19);
child1.colors.push("pink");
console.log(child1.colors); // ["red", "blue", "yellow", "pink"]
child1.sayAge(); // 19
child1.sayName(); // "lili"
let child2 = new Child("yue", 22);
console.log(child2.colors); // ["red", "blue", "yellow"]
child2.sayAge(); // 22
child2.sayName(); // "yue"
6.2class继承
class Person{
constructor(name,age){
this.name = name
this.age = age
}
}
class Child extends Person{
constructor(name,age,gender){
super(name,age)//通过super调用父类的构造方法
this.gender = gender
}
}
let child1 = new Child('lili',24,'female')
console.log(child1) // {name: 'lili', age: 24, gender: 'female'}
7.数组排序
array = [2,1,5,3,8,4,9,5]
7.1冒泡排序
- 在一次循环中相邻的两个元素进行循环比较,如果前者大(小)于后者,则交换位置,这样做的结果就是第一次循环最大(小)的排到了最后;
- 重复这样的动作,重复多少次,由数组的长度决定,比如length为2,重复一次就行,length为3,就重复两次..依次类推,要重复length-1次;
function sort(arr){
//外层循环控制比较的轮数
for(var i=0;i<arr.length-1;i++){
for(var j=0;j<arr.length-i-1;j++){
//内层循环控制交换的次数
if(arr[j]>arr[j+1]){
var temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
return arr;
}
let array=sort([2,1,5,3,8,4,9,5])
console.log(array)
7.2插入排序
对于一个数组,先从中取出第一个项形成新的数组,其后每次往新的数组中插入新的值,每次插完后都要进行排序,直到插完为止;
// 插入排序
function insertSort(arr) {
let arr1= [arr[0]];
for (let i = 1; i < arr.length; i++) {
// 插入多少次
arr1.push(arr[i]);
// 排序只需要循环length-1次
for (let j = 0;j < arr1.length - 1;j++){
// 每次插入要做的事情
if (arr1[arr1.length-j-1] < arr1[arr1.length-j-2]){
// 交换位置
var arrTem = arr1[arr1.length-j-2];
arr1[arr1.length-j-2] = arr1[arr1.length-j-1];
arr1[arr1.length-j-1] = arrTem;
}
}
}
return arr1
}
var arr = [2,1,5,3,8,4,9,5]
console.log(insertSort(arr))
7.3选择排序
选择法排序是指每次选择所要排序的数组中值最小的数组元素(按从小到大的顺序排序,如果按从大到小的顺序排序,则选择值最大的数组元素),将这个数组元素的值与最前面没有进行排序的数组元素的值互换。
let sort = (numbers) => {
for(let i=0; i<numbers.length -1; i++){
let index = minIndex(numbers.slice(i))+ i
// index对应的数应该放到i处
if(index!==i){swap(numbers, index, i)
}
}
return numbers
}
let swap = (array, i ,j) =>{
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
let minIndex = (numbers) =>{
let index = 0
for(let i=1; i<numbers.length; i++){
if(numbers[i] < numbers[index]){
index = i
}
}
return index
}
let array = [2,1,5,3,8,4,9,5]
sort(array)
7.4快速排序
Quick sort 以某某为基准,小的去前,大的去后。
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 arr = [2,1,5,3,8,4,9,5]
quickSort(arr);
7.5合并排序
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)))
}
arr = [2,1,5,3,8,4,9,5]
mergeSort(arr);
8.promise
在没有Promise之前,如果我们想要操作多个异步函数,会有回调地狱
8.1创建一个Promise实例
Promise 对象是由关键字 new 及其构造函数来创建的。该构造函数接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。
resolve函数和reject函数都只接受一个参数
let myFirstPromise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
console.log("Yay! " + successMessage);
}, function(errorMessage){
// reject会调用这个函数
});
8.2Promise.prototype.then()
then() 方法返回一个 Promise 。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
const promise1 = new Promise((resolve, reject) => {
resolve('Success!');
});
promise1.then((value) => {
console.log(value);
// expected output: "Success!"
});
then() 方法会返回一个新的promise对象,实现链式调用
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)
});
8.3Promise.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的回调函数。
var p = Promise.all([1,2,3]);
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
var p3 = Promise.all([1,2,3, Promise.reject(555)]);
setTimeout(function(){
console.log(p);
console.log(p2);
console.log(p3);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[3] }
// Promise { <state>: "fulfilled", <value>: Array[4] }
// Promise { <state>: "rejected", <reason>: 555 }
8.4Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// 两个都完成,但 p2 更快
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then(function(value) {
console.log(value); // "three"
// p3 更快,所以它完成了
}, function(reason) {
// 未被调用
});
var p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then(function(value) {
// 未被调用
}, function(reason) {
console.log(reason); // "six"
// p6 更快,所以它失败了
});
9.跨域
9.1同源
浏览器为了保护用户隐私和数据安全制定了同源策略,同源策略规定:如果两个URL的协议、域名和端口号都一致,这两个URL就是同源的
9.2跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。请求可以发出,但是响应会被浏览器阻止
9.3 JSONP 跨域
核心思想:利用script标签可以跨域的特性。
- 站点A声明一个回调函数,函数参数是想要获取的数据,创建script标签,src是跨域请求的url,并且将函数名以问号传参的方式发送给站点B;
- 站点B收到请求后,后端改造JS文件内容,将函数名和数据拼接成字符串返回给站点A
- 站点A这边接收到响应,执行回调函数,就可以拿到数据
- 缺点:由于是script标签,只能发get请求,不支持post请求
- 优点:兼容性好
9.4 CORS 跨域
- 对于简单请求,浏览器发送的请求报文包含Origin字段,被请求站点在响应头添加
Access-Control-Allow-Origin: http://请求站点 - 对于复杂请求,浏览器会先使用OPTIONS方法发起一次预检请求,服务器需要设置如下响应头 预检请求完成后,再发送实际请求,服务器设置响应头
Access-Control-Allow-Origin: http://请求站点