# ES6

# ES6新特性

  1. let( let 允许创建块级作用域(最靠近的一个花括号内有效),不具备变量提升,不允许重复声明: )、const( const 允许创建块级作用域(最靠近的一个花括号内有效)、变量声明不提升、const 在声明时必须被赋值、声明时大写变量(默认规则): )、block作用域
  2. 箭头函数 ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:
  3. 函数默认参数值 ES6 中允许你对函数参数设置默认值:
  4. 对象超类 ES6 允许在对象中使用 super 方法:
  5. Map VS WeakMap ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。 一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:
  6. 类 ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。 函数中使用 static 关键词定义构造函数的的方法和属性:

# Var let const 的区别

共同点:都能声明变量
不同点:var 在ECMAScript 的所有版本中都可以使用,而const和let只能在ECMAScript6【ES2015】及更高版本中使用

var let const
作用域 函数作用域 块作用域 块作用域
声明提升 不能 不能
重复声明 不能 不能
全局声明时为window对象的属性 不是 不是

# var

ECMAScript6 增加了let 和 const 之后要尽可能少使用var。因为let 和 const 申明的变量有了更加明确的作用域、声明位置以及不变的值。 优先使用const来声明变量,只在提前知道未来会修改时,再使用let。

# let

  • 因为let作用域为块作用域!!!!【得要时刻记住这一点】
  • 不能进行条件式声明
  • for循环使用let来声明迭代变量不会导致迭代变量外渗透。

# const

  • 声明时得直接初始化变量,且不能修改const声明的变量的值
  • 该限制只适用于它指向的变量的引用,如果它为一个对象,则可以修改这个对象的内部的属性。

# 实现继承的几种方式

# 原型链继承

父类的实例作为子类的原型


# Nullundefined 的区别

  • undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。.
  • null 则表示"什么都没有",即"空值"。
  • Javascript将未赋值的变量默认值设为 undefined
  • Javascript从来不会将变量设为null,它是用来让程序员表明某个用var声明的变量时没有值的;

# Call bind apply方法的区别

# apply方法

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window( 在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。

# call方法

call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

# bind方法

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表( 但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

# 前端数据持久化的理解

前端缓存分为HTTP缓存和浏览器缓存

注意

其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器缓存则主要由前端开发在前端js上进行设置。

# 使用缓存做性能优化

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且 由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

对于一个数据请求来说,可以分为发起网络请求后端处理浏览器响应 三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

# 强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种

  • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求

  • 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存

  • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

# 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程

  • 协商缓存生效,返回304

  • 协商缓存失效,返回200和请求结果

# 防抖和节流

# 防抖(debounce)

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。 非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

# 节流(throttle)

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版

# 闭包

# 变量作用域

要理解闭包,首先要理解 JavasSript 的特殊的变量作用域。

变量的作用域无非就两种:全局变量和局部变量

JavasSript 语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量

注意

在函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明的是一个全局变量!

# 如何从外部读取函数内部的局部变量?

出于种种原因,我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。

那就是在函数内部,再定义一个函数。

function f1() {
    var n = 999;

    function f2() {
        alert(n)//999
    }
}

在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。

这就是 JavasSript 语言特有的"链式作用域"结构(chain scope),

子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!

# 闭包的概念

上面代码中的 f2 函数,就是闭包。 各种专业文献的闭包定义都非常抽象,闭包就是能够读取其他函数内部变量的函数

由于在 JavaScript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁

# 闭包的用途

闭包可以用在许多地方。它的最大用处有两个:

  1. 前面提到的可以读取函数内部的变量.
  2. 让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除。 为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

# 使用闭包的注意点

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

# 深浅拷贝

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会 改到原对象。

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含了子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

# 原型链

# 什么是原型链呢?

简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。

# 原型对象和实例之间有什么作用呢?

通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。

在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。

# prototype:

prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象 ,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);

# proto:

proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。

注意

为什么Foo构造也有这个属性呢,因为再js的宇宙里万物皆对象,包括函数

# constructor:

我们看到途中最中间灰色模块有一个constructor属性,这个又是做什么用的呢?

每个函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身。

此外,我们还可以使用constructor属性,所有的实例对象都可以访问constructor属性,constructor属性是创建实例对象的函数的引用。我们可以使用constructor属性验证实例的原型类型(与操作符instanceof非常类似)。

# Require 和 import的区别

  1. import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载 ,所以require理论上可以运用在代码的任何地方,所以import性能更好。

  2. import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝, 我们可以理解为深拷贝。

  3. import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好。

  4. import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发。

  5. import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式。

  6. 目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中, import和require都会变为_webpack_require_。

上次更新: 3/31/2023, 3:36:19 PM