前端进阶高薪必看-JS篇(下)

2,571 阅读11分钟

思维导图链接(高清无码)

图中1, 2, 3, 4 表示优先级 img

use strict是什么意思 ? 使用它区别是什么?

use strict 是一种 ECMAscript5 添加的严格模式运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:

  1. 消除 Javascript 语法的不合理不严谨之处,减少怪异行为;

  2. 消除代码运行的不安全之处,保证代码运行的安全;

  3. 提高编译器效率,增加运行速度;

  4. 为未来新版本的 Javascript 做好铺垫。 区别:

  5. 禁止使用 with 语句。

  6. 禁止 this 关键字指向全局对象。

  7. 对象不能有重名的属性。

如何判断一个对象是否属于某个类?

  1. 第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
  2. 第二种方式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。
  3. 第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的[[Class]] 属性来进行判断。

for...in 和 for...of 的区别

for…of 是 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构(数组、对象等)并且返回各项的值,和 ES3 中的 for…in 的区别如下

  1. for…of 遍历获取的是对象的,for…in 获取的是对象的键名
  2. for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
  3. 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

如何使用 for...of 遍历对象

for…of 是作为 ES6 新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。

如果需要遍历的对象是类数组对象,用 Array.from 转成数组即可。

var obj = {
    0:'one',
    1:'two',
    length: 2
};
obj = Array.from(obj);
for(var k of obj){
    console.log(k)
}

如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。

//方法一:
var obj = {
    a:1,
    b:2,
    c:3
};

obj[Symbol.iterator] = function(){
    var keys = Object.keys(this);
    var count = 0;
    return {
        next(){
            if(count<keys.length){
                return {value: obj[keys[count++]],done:false};
            }else{
                return {value:undefined,done:true};
            }
        }
    }
};

for(var k of obj){
    console.log(k);
}


// 方法二
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){
    var keys = Object.keys(obj);
    for(var k of keys){
        yield [k,obj[k]]
    }
};

for(var [k,v] of obj){
    console.log(k,v);
}

ajax、axios、fetch 的区别

AJAX

Ajax 是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。

AJAX 的缺点:

  1. 本身是针对 MVC 编程,不符合前端 MVVM 的浪潮
  2. 基于原生 XHR 开发,XHR 本身的架构不清晰
  3. 不符合关注分离的原则
  4. 配置和调用方式非常混乱,而且基于事件异步模型不友好。

Fetch

fetchES6 中基于 promise 设计的 AJAX 替代品。Fetch 的代码结构比起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生js,没有使用 XMLHttpRequest 对象。

fetch的优点:

  1. 语法简洁,更加语义化
  2. 基于标准 Promise 实现,支持 async/await
  3. 更加底层,提供的API丰富(request, response)
  4. 脱离了XHR,是ES规范里新的实现方式

fetch的缺点:

  1. 只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject
  2. 默认不会带 cookie,需要添加配置项: fetch(url, {credentials: 'include'})
  3. 不支持 abort,不支持超时控制,造成了流量的浪费
  4. 没有办法原生监测请求的进度,而 XHR 可以

Axios

Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下:

  1. 浏览器端发起 XMLHttpRequests 请求
  2. node 端发起 http 请求
  3. 支持 Promise API
  4. 监听请求和返回
  5. 对请求和返回进行转化
  6. 取消请求
  7. 自动转换 json 数据
  8. 客户端支持抵御 XSRF 攻击

数组的遍历方法有哪些

方法是否改变原数组特点
forEach()取决于元素的数据类型数组方法,没有返回值,是否会改变原数组取决于数组元素的类型是基本类型还是引用类型,详细解释可参考文章:《forEach到底可以改变原数组吗》
map()数组方法,不改变原数组,有返回值,可链式调用
filter()数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用
for...offor...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环
every() 和 some()数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
find() 和 findIndex()数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce() 和 reduceRight()数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

对作用域、作用域链的理解

全局作用域

  1. 最外层函数与变量拥有全局作用域
  2. 未定义直接赋值的变量为全局作用域
  3. 所有 window 对象的属性拥有全局作用域
  4. 全局作用域容易引起命名冲突

函数作用域

  1. 函数内部的变量
  2. 作用域是分层的,内层作用域可以访问外层作用域,反之不行

块级作用域

  1. letconst 声明的变量有块级作用域
  2. letconst 声明的变量不会有变量提升,也不可以重复声明
  3. 循环中比较适合绑定块级作用域,把声明的计数器变量限制在循环内部

作用域链:

定义: 在当前作用域中查找所需变量,如果该作用域没有这个变量,依次上级作用域查找,直到访问到 window 对象就被终止,这就是作用域链

作用: 保证对对变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

本质: 指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的第一个是当前作用域的变量对象。最后一个是全局对象。

什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

回调函数:

ajax(url, () => {
    // 处理逻辑
})

回调函数有一个致命的弱点,就是容易写出回调地狱。如下代码:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

回调函数的缺点:

  1. 容易写出回调地狱
  2. 嵌套函数存在耦合性,不易维护
  3. 嵌套函数一多,就很难处理错误
  4. 不能直接 return
  5. 不能用 try catch 捕捉错误

对象创建的方式有哪些?

一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。

常见的有以下几种:

  1. 第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。

  2. 第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

  3. 第三种是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

  4. 第四种是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。

  5. 第五种是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。

  6. 第六种是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。

对象继承的方式有哪些?

  1. 第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

  2. 第二种是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

  3. 第三种是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

  4. 第四种是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

  5. 第五种是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。

  6. 第六种是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

浏览器的垃圾回收机制

垃圾回收(GC)的概念: JavaScript 代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收

垃圾回收的两种方式:

1. 标记清除(大部分浏览器的实现方式): 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后它会去掉被标记为引用的变量,剩下的将被视为准备回收的变量。最后垃圾回收器清除被标记的变量,释放其内存

2. 引用计数(少部分浏览器的实现方式): 每次引用加一,被释放时减一,当这个值的引用次数变成 0 时,就可以将其内存空间回收

引用计数的缺点: 循环引用(obj1 和 obj2 通过各自的属性相互引用,也就是说,这两个对象的引用次数都是 2)

function fun() {
    let obj1 = {};
    let obj2 = {};
    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}

这种情况下,就要手动释放变量占用的内存:

obj1.a =  null
obj2.a =  null

垃圾回收优化

虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。

  1. 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
  2. object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为 null,尽快被回收。
  3. 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面

哪些情况会导致内存泄漏

以下四种情况会造成内存的泄漏:

  1. 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
  2. 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
  3. 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
  4. 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。



完结,撒花 ✿✿ヽ(°▽°)ノ✿



点赞 o( ̄▽ ̄)d 不迷路

收藏 ✋🏻 + 关注

谢谢老铁 ♪(・ω・)ノ