前端常见问题总结

2022-08-08  本文已影响0人  饥人谷_折纸大师

什么是闭包?闭包的用途是什么?闭包的缺点是什么?

什么是闭包

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量。声明在一个函数中的函数,叫做闭包函数。
存在不被释放或者互相引用的场景都可以叫做闭包。
代码举例:

function Counter(){
    let num = 0
    function add(){
        num++
        console.log(num)
    }
    return add
}
let addOne = Counter()
addOne()//1
addOne()//2

在Counter函数中创建了一个add函数,且add函数中用到了Counter函数的num变量,这就使得num和add函数构成了一个闭包。这个变量num一直存在于内中,我们很难在外部访问num变量,但是我们可以通过操作内部的函数来控制它。

闭包的用途
  1. 隐藏变量,只通过制定接口访问数据
const cache = (() => {
 const store = {}
 return {
     get(key) {
         return store[key]
     },
     set(key, val) {
         store[key] = val
     },
     remove(key) {
         delete store[key]
     }
 }
})()

这个箭头函数我们会得到一个对象,我会得到它的get,set和remove方法,我们打印这个cache的时候只会看到get,set和remove三个属性,store就被我们隐藏起来了。但是我们可以通过get,set和remove来操作store。

  1. 存储临时变量
function sum(a){
 return function(b){
     return a+b
 }
}
const sum1 = sum(1)
const sum2 = sum(2)
console.log(sum1(3))//4
console.log(sum2(3))//5

声明好的sum1和sum2就会隐藏在闭包里,当我们在调用sum1(3)的时候就自动加上了sum1,同理在调用sum2(3)的时候,也自动加上了sum2。sum1和sum2就存储了一个临时的数据给我们用。

  1. 让对象拥有私有属性
 constructor() {
     let name
     Object.assign(this,{
         setName(newName){
             name = newName
         },
         getName(){
             return name
         }
     })
 }
 sayHi(){
     console.log(`Hi,${this.getName()}`)
 }
}
let p1 = new People
p1.setName('qyk')
p1.getName()//'qyk'
p1.sayHi()//"Hi,qyk"

sayHi()调用了上层的name,是个闭包。同时也让p1有了私有属性。

  1. 调用一个函数返回另一个函数
闭包的缺点
  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

call、apply、bind 的用法分别是什么?

call

call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、
undefined的时候,默认指向window。

function add(a,b){
  return a+b;
}
function.call(add,1,2)//3 其中1和2是连续的参数
apply

apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为
null、undefined的时候,默认指向window。

function add(a,b){
  return a+b;
}
funciton.apply(add,[1,2]//3 其中1和2数参数数组

当函数需要传递多个变量时, apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量。

bind

第一个参数是this的指向,从第二个参数开始是接收的参数列表。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。

fn.bind(x,y,z) 不会执行 fn,而是会返回一个新的函数
新的函数执行时,会调用 fn,调用形式为 fn.call(x, y, z),其中 x 是 this,y 和z 是其他参数

function add(a, b){
  return a+b;
}
var foo1 = add.bind(add, 1,2); 
foo1(); //3 只有调用才会执行

在 ES6 的箭头函数下, call 和 apply 将失效。


请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。

  1. 状态码100表示继续。客户端应继续其请求
  2. 状态码101表示切换协议。服务器根据客户端的请求切换协议
  3. 状态码200表示请求成功。一般用于GET与POST请求
  4. 状态码201表示已创建。成功请求并创建了新的资源
  5. 状态码202表示已接受。已经接受请求,但未处理完成
  6. 状态码203表示非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
  7. 状态码300表示多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
  8. 状态码303表示查看其它地址
  9. 状态码305表示使用代理。所请求的资源必须通过代理访问
  10. 状态码306表示已经被废弃的HTTP状态码
  11. 状态码400表示客户端请求的语法错误,服务器无法理解
  12. 状态码403表示服务器理解请求客户端的请求,但是拒绝执行此请求
  13. 状态码404表示服务器无法根据客户端的请求找到资源(网页)

如何实现数组去重?

假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数 unique,使得
unique(array) 的值为 [1,5,2,3,4]
也就是把重复的值都去掉,只保留不重复的值。

使用indexOf

思路:新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,
如果有相同的值则跳过,不相同则push进数组。

let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
function unique(arr) {
 let array = [];
 for (let i = 0; i < arr.length; i++) {    // 首次遍历数组
     if (array.indexOf(arr[i]) === -1) {   // 判断索引有没有等于
         array.push(arr[i])
     }
 }
 return array
}
console.log(unique(arr));

缺点:无法对NaN和对象去重

使用set
let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
function unique (arr) {
 return Array.from(new Set(arr))
}
console.log(unique(arr))

缺点:无法去重对象,且有兼容性问题。API太新,旧浏览器不支持

使用map
function unique(arr) {
    const map = new Map()
    const newArr = []

    arr.forEach(item => {
        if (!map.has(item)) { // has()用于判断map是否包为item的属性值
            map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
            newArr.push(item)
        }
    })

    return newArr
}

console.log(unique(arr))

缺点:API 太新,旧浏览器不支持。


DOM 事件相关

什么是事件委托

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件委托。 通俗点讲就是委托一个元素帮我监听我本该监听的元素。

怎么阻止默认动作

w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

<a href="http://www.cnblogs.com/yycode/" id="testA" >caibaojian.com</a>
 var a = document.getElementById("testA");
 a.onclick =function(e){
     if(e.preventDefault){
         e.preventDefault();//W3C
     }else{
         window.event.returnValue = false;//IE
     }
}
怎么阻止事件冒泡

w3c使用e.stopPropagation(),IE使用e.cancelBubble = true

 function stopBubble(e) { 
     if ( e && e.stopPropagation ){ 
         e.stopPropagation(); //W3C
     }else{ 
         window.event.cancelBubble = true; //IE
     }
}

如何理解 JS 的继承?

基于原型的继承

实例化一个新的函数,子类的原型指向了父类的实例,子类就可以调用其父类原型对象上的共有属性。

function Parent() {
    this.parentName = '父类';
}
Parent.prototype.getParentName = function() {
    return this.parentName;
};

function Child() {
    this.childName = '子类';
}
Child.prototype = new Parent();//继承Parent
Child.prototype.getChildName = function() {
    return this.childName
};

let c = new Child();
console.log(c.getParentName()); // '父类'
function Parent(name1){
  this.name1 = name1
}
Parent.prototype.pMethod = function(){
  console.log(this.name1)
}

function Child(name2, name1){
    Parent.call(this, name1) // 得分点
    this.name2 = name2
}
Child.prototype.__proto__ = Parent.prototype 
Child.prototype.cMethod = function(){
    console.log(this.name2)
}

缺点:子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,不安全

基于class的继承

如需创建类继承,使用 extends 关键字。

class Parent{
    constructor(name1){
        this.name1 = name1
    }
    pMethod(){
        console.log(this.name1)
    }
}
class Child extends Parent{
    constructor(name2, name1){
        super(name1) // 得分点
        this.name2 = name2
    }
    cMethod(){
        console.log(this.name2)
    }
}

数组排序

给出正整数数组 array = [2,1,5,3,8,4,9,5]
请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]
新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。

let min = (numbers)=>{
    if(numbers.length>2){
        return min([numbers[0],min(numbers.slice(1))])
    }else {
        return Math.min.apply(null,numbers)
    }
}//求出最小值的算法
let minIndex = (numbers) =>
    numbers.indexOf(min(numbers))//标记最小值

let sort = (numbers)=>{
    if(numbers.length>2){
        let index = minIndex(numbers)
        let min = numbers[index]
        numbers.splice(index,1)
        return [min].concat(sort(numbers))
    }else{
        return numbers[0]<numbers[1]? numbers :numbers.reverse()
    }
}
let array =[2,1,5,3,8,4,9,5]
sort(array)

对 Promise 的了解?

promise的用途

Promise 用于避免回调地域,让代码看起来更同步

创建一个 new Promise
function fn(){
    return new Promise((resolve, reject)=>{
        成功时调用 resolve(data)
        失败时调用 reject(reason)
    })
}
使用 Promise.prototype.then
const promise1 = fn() // 得到 promise1 对象
fn().then(success, fail).then(success2, fail2).catch(fail3)
或者
promise1.then(success, fail).then(success2, fail2).catch(fail3)
均可
使用Promise.all
Promise.all([promise1, promise2]) 并行,等待所有 promise 成功。
如果都成功了,则 all 对应的 promise 也成功;如果有一个失败了,则 all 对应的 promise 失败。
使用 Promise.race
Promise.race([promise1, promise2]),返回一个
promise,一旦数组中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

说说跨域

什么是同源

源:协议+域名+端口号
Window.origin或者location.origin可以得到当前的源
两个URL的协议、域名和端口号完全一致那么这两个URL就是同源
同源策略就是,浏览器规定:如果JS运行在源A里,那么就只能获取源A的数据,不能获取源B的数据,
即不允许跨域。这是浏览器的功能。浏览器为了主动预防偷数据的问题,设置了严格的同源策略

什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。
它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

JSONP跨域

我们在跨域的时候,由于当前浏览器不支持CORS,或者因为某些条件不支持CORS,我们必须使用一种方法来进行跨域。于是我们请求一个JS文件,这个JS文件回执行一个回调,回调里面有我们的数据。回调的名字可以通过随机数生成的,我们把这个随机数以callback的参数传给后台,后台会把函数返回给我们并且执行。
缺点:由于它是一个script标签,所以读不到AJAX那么精确的status值,无法知道状态码是什么,也只能发送GET请求,JSONP不支持POST

CORS跨域

CORS(跨域资源共享)
跨源资源共享 (CORS)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。浏览器默认不同源之间不能互相访问数据,但是我们想让两个网站互相访问。我们就用CORS,如果要共享数据就需要提前声明。例如,源B要访问源A,源A就要在响应头里声明源B可以访问:
response.setHeader("Access-Control-Allow-Origin","http://foo.example")

上一篇下一篇

猜你喜欢

热点阅读