阶段性复习

60 阅读5分钟

1. 闭包?用途?缺点?

什么是闭包

「函数使用了外部的变量」

局部变量 local + 函数 foo,就是一个闭包

「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。

能够实现函数外部读取函数内部变量的就是闭包。闭包是将函数内部和函数外部连接起来的桥梁

image.png

闭包的用途

闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。

    const createAdd = ()=>{
        let n = 0
        return ()=>{
            n += 1
            console.log(n)
        }
    }

    const add = createAdd()
    add() // 1
    add() // 2
  1. 可以读取函数内部的变量
  2. 让这些变量的值始终保持在内存中。不会在调用结束后,被垃圾回收机制回收。
  3. 隐藏局部变量,暴露操作函数

闭包的缺点

比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。

2. call、apply、bind 的用法

1. call

第一个参数是 this,后面的参数是 arguments 或其他参数

function.call(thisArg, arg1, arg2, ...)

2. apply

第二个参数必须是数组,内含所有其他参数

apply(thisArg)
apply(thisArg, argsArray)

3. bind

bind 的用法有两点

  • fn.bind(x,y,z) 不会执行 fn,而是会返回一个新的函数
  • 新的函数执行时,会调用 fn,调用形式为 fn.call(x, y, z),其中 x 是 this,y 和z 是其他参数
function.bind(thisArg[, arg1[, arg2[, ...]]])
const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};
const unboundGetX = module.getX;
// 该函数在全局范围内调用
console.log(unboundGetX());
// 输出为:undefined
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// 输出为:42

三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。 bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。

3. HTTP 状态码

200 : 访问成功,正常状态。
301 : 永久移动,表示本网页已经永久性的移动到一个新的地址,在客户端自动将请求地址改为服务器返回的新地址。
302 : 临时重定向,表示网页暂时性的转移到一的新的地址,客户端在以后可以继续向本地址发起请求
303 : 表示必须临时重定向,并且必须使用GET方式请求。
304 : 重定向至浏览器本身,当浏览器多次发起同一请求,且内容未更改时,使用浏览器缓存,这样可以减少网络开销。
401 : 表示协议格式出错,可能是此IP地址被禁止访问该资源,与403类似。
403 : 表示没有权限,服务器拒绝访问请求。
404 : 表示找不到系统资源,但是只是暂时性地。
500 : 表示服务器程序错误,一个通用的错误信息。
503 : 表示服务器繁忙,或者服务器负载,通常这只是一个临时状态。

4. 数组去重

1. 不使用 Set 实现

      const array = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4];
      function unique(array) {
        let newArr = [];
        for (let i = 0; i < array.length; i++) {
          if (newArr.indexOf(array[i]) === -1) {
            newArr.push(array[i]);
          }
        }
        return newArr;
      }
      console.log(unique(array));

代码容易变得冗长且容易出错;数组原来的排序会被打乱,会破坏原数组的稳定性

2. 使用 Set

      const array = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4];
      console.log([...new Set(array)]);

可能出现浏览器的兼容性问题;这种方法还无法去掉{}空对象

3. 使用 Map / WeakMap 以支持对象去重的

      function unique(arr) {
        let map = new Map();
        let newArr = new Array();
        for (let i = 0; i < arr.length; i++) {
          if (!map.has(arr[i])) {
            // Map 里面key是唯一的
            map.set(arr[i], true);
            newArr.push(arr[i]);
          }
        }
        return newArr;
      }
      console.log(unique(array));

代码较为复杂;需要使用临时的空间,Map 数据结构,存储中间结果

5. DOM 事件相关

1. 什么是事件委托?

  • 原理:利用事件冒泡原理,将子元素的操作委托给父元素或是祖先元素统一管理。
  • 当需要给后代元素执行多个重复事件时,可通过事件委托一次性管理操作来减少事件触发,节约内存。
  • 动态监听元素:当需要给动态添加的元素添加事件时,必须通过事件委托来委托给父辈元素或祖先元素。

2. 怎么阻止默认动作?

让链接不跳转 或者让提交按钮不提交

function stopDefault( e ) { 
    //阻止默认浏览器动作(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函数器默认动作的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}

3. 怎么阻止事件冒泡?

function stopBubble(e) { 
//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //因此它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //否则,我们需要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}

JS 的继承

A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法

1. 基于原型

子类型的原型为父类型的一个实例对象。

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 
//上面这句代码的古板写法应该是下面三句
//const empty = function(){}
//empty.prototype = Parent.prototype
//Child.prototype = new empty()

Child.prototype.cMethod = function(){
    console.log(this.name2)
}

将子类的原型指向父类的实例,所以子类的实例就可以通过__proto__访问到 Student.prototype 也就是Person的实例,这样就可以访问到父类的私有方法,然后再通过__proto__指向父类的prototype就可以获得到父类原型上的方法。

子类继承父类的属性和方法是将父类的私有属性和公有方法都作为自己的公有属性和方法

优点:简单、易于实现,父类的新增的实例与属性子类都能访问

缺点:

  • 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
  • 无法实现多继承
  • 创建子类实例时,不能向父类构造函数中传参数
  • 来自原型对象的所有属性被所有实例共享

2. class 的继承

将父类实例对象的属性和方法,加到this上面(需先调用super方法),然后再用子类的构造函数修改this。
class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的

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)
    }
}

优点:语法简单易懂,操作更方便
缺点:不是所有的浏览器都支持

数组排序

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 = [2, 1, 5, 3, 8, 4, 9, 5];
      console.log(sort(array));

2. 希尔排序(性能最好的排序)

      function xier(arr) {
        let interval = parseInt(arr.length / 2); //分组间隔设置
        while (interval > 0) {
          for (let i = 0; i < arr.length; i++) {
            let n = i;
            while (arr[n] < arr[n - interval] && n > 0) {
              let temp = arr[n];
              arr[n] = arr[n - interval];
              arr[n - interval] = temp;
              n = n - interval;
            }
          }
          interval = parseInt(interval / 2);
        }
        return arr;
      }
      let arr = [2, 1, 5, 3, 8, 4, 9, 5];
      console.log(xier(arr));

Promise

1. Promise 的用途

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

2. 创建一个 new Promise

return new Promise((resolve, reject)=>{})
function fn(){
    return new Promise((resolve, reject)=>{
        成功时调用 resolve(data)
        失败时调用 reject(reason)
    })
}

3. Promise.prototype.then

Promise.then()方法返回一个 promise 实例
它最多需要有两个参数:Promise 的成功和失败情况的回调函数

const promise1 = new Promise((resolve, reject) => {
  resolve('Success!');
});

promise1.then((value) => {
  console.log(value);
  // expected output: "Success!"
});

promise1.then(success, fail).then(success2, fail2).catch(fail3)

4. Promise.all

Promise.all([promise1, promise2]) 并行,等待所有 promise 成功。
如果都成功了,则 all 对应的 promise 也成功;如果有一个失败了,则 all 对应的 promise 失败。

5. Promise.race

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// expected output: "two"

跨域

1. 什么是同源

源 = 协议 + 域名 + 端口
如果两个 url 的协议、域名、端口号完全一致,那么这两个 url 就是同源的

2. 什么是跨域

跨域即不同源之间的页面相互访问数据。浏览器规定不同源的页面之间,不准互相访问数据,即不允许跨域

3. JSONP 跨域

我们可以请求一个JS文件,这个文件会执行一个回调,回调里面有数据
回调名字是随机生成的一个随机数。我们把这个名字以callback的参数传给后台,后台再返回给我们并执行。

  • JSONP当前网站创建一个script去请求另一网站的JS,JS会夹带数据,数据会在当前网站上调用全局函数运行。
  • 优点:兼容IE,可以跨域
  • 缺点:由于是script标签,所以读不到状态码,只知道成功或者失败,也只能发GET请求,不支持POST

4. CORS 跨域

由于浏览器默认不同源之间不能互相访问数据,所以如果要共享数据,需要提前说明。这就是CORS跨域的原理。

例如只需要qq.com在响应头里写frank.com可以访问,也就是以下形式:

Access-Control-Allow-Origin: https://frank.com