# ES6
# ES6新特性
- let( let 允许创建块级作用域(最靠近的一个花括号内有效),不具备变量提升,不允许重复声明: )、const( const 允许创建块级作用域(最靠近的一个花括号内有效)、变量声明不提升、const 在声明时必须被赋值、声明时大写变量(默认规则): )、block作用域
- 箭头函数 ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:
- 函数默认参数值 ES6 中允许你对函数参数设置默认值:
- 对象超类 ES6 允许在对象中使用 super 方法:
- Map VS WeakMap ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。 一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:
- 类 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声明的变量的值
- 该限制只适用于它指向的变量的引用,如果它为一个对象,则可以修改这个对象的内部的属性。
# 实现继承的几种方式
# 原型链继承
父类的实例作为子类的原型
# Null 和 undefined 的区别
undefined表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。.null则表示"什么都没有",即"空值"。Javascript将未赋值的变量默认值设为undefinedJavascript从来不会将变量设为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 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
# 闭包的用途
闭包可以用在许多地方。它的最大用处有两个:
- 前面提到的可以读取函数内部的变量.
- 让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除。 为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
# 使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(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的区别
import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载 ,所以require理论上可以运用在代码的任何地方,所以import性能更好。
import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝, 我们可以理解为深拷贝。
import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好。
import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发。
import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式。
目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中, import和require都会变为_webpack_require_。
← 箭头函数