Menu

js知识总结—ES6篇(一)

1.列举ES6的一些新特性

  1. 默认参数
  2. 模板字符串
  3. 解构赋值
  4. 增强的对象字面量
  5.  箭头函数
  6. Promises 异步
  7. generator和async/await
  8. 块作用域 和let和const
  9. Class 类
  10. Modules 模块

2.let ,const,var及其区别

JS代码在执行前会进行预解析。预解析会进行变量提升。

var 声明的变量会发生提升(提升到当前作用域顶部)。虽然变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做提升,并且提升的是声明。

注意:是申明的提升,赋值并不会提升。赋值和申明并不同时发生。只有先声明才能使用这个变量,后申明的值前面无法直接使用。

(这里注意undefined的写法,避免手写代码时写错)

console.log(a) // undefined,如果后面没有申明变量a,这里就不是undefined而是直接报错:is not defined
var a = 1
// 同理如下
var a1 = a2+1
var a2 = 10
console.log(a1) // NaN
//----------------------------------------
var a = 10
var a
console.log(a) // 10
//原因:var会忽略同一个变量声明,在一个作用域中,如果已经有一个变量使用var声明了,那JS会忽略后续的同一个变量声明。
var a = 'hello world';
var a = undefined;
console.log(a) //undefined
// var a = undefined虽然作用等同于var a 但由于赋值的覆盖,所以会打印undefined
//----------------------------------------
console.log(a) // ƒ a() {} 这就是为什么函数可以放到下面,在上面依然能调用的原因
function a() {}
var a = 1 // 函数会被提升,且优先于变量
//-----------------------------------------
let a = 1
let a = 2 //error:Identifier 'a' has already been declared
// let 无法重复声明变量

let和const 声明的变量不会提升。

  1. let 和 const 在全局作用域下声明变量,变量并不会被挂载到 window 上,而 var 会被挂载到window对象中。
  2. let 和 const 在声明 变量 之前如果使用了这个变量,就会出现报错,是因为存在暂时性死区。(let实际也会进行提升,只不过因为暂时性死区导致了并不能在声明前使用。)
  3. let和const无法重复声明,而var可以重复申明,且var后声明的值会覆盖前值(同名函数也会覆盖之前的函数)。如果后申明的重复变量未赋值,会被忽略。
  4. var没有块级作用域,只有函数作用域。而let和const有块级作用域
  5. const申明了变量后必须赋值。不赋值会报错。(const a;是错误的)
  6. const定义的常量不能被修改。
console.log(a) // Uncaught ReferenceError: a is not defined
let a // 这里将提升限制了,所以提示a未申明

函数作用域:

function fn(){
   var aaa = 1
}
fn()
console.log(aaa) // 报错,未定义

经典面试题:

var a = 1;
fn();
function fn(){
    console.log(a); // undefined
    var a = 2
    console.log(a) // 2
}

// 第一个打印 undefined 是由于
// 函数申明也会被提升,且优先于变量
// 函数作用域中的变量的申明也会提升到函数最顶部
// 函数作用域中变量为局部变量,作用域链优先取局部值,局部没有才会找外层
// 上述代码等同于下:

function fn(){
    var a
    console.log(a); // undefind
    a = 2
    console.log(a) // 2
}
var a = 1;

面试题2

function fn(){
    console.log(a); // undefind
    a = 2
    console.log(a) // 2
}
fn() // 在这里执行时候,a只是提升申明,还没有赋值
var a = 1;

//----------------------------

function fn(){
    console.log(a); // 1
    a = 2
    console.log(a) // 2
}
var a = 1;
fn() // 在这里执行,a已经被赋值

块级作用域:

js有全局作用域、函数作用域。在ES6之后新增块级作用域。也就是`{}`中的作用域。if和for语句是典型的块级作用域。var在函数作用域中不会垮函数访问,而可以垮块级访问。也就是说var只承认函数作用域。
for(var i=0;i<3;i++){
    console.log("a:",i)
}
console.log("b:",i)
// a: 0
// a: 1
// a: 2
// b: 3

for(let j=0;j<3;j++){
    console.log("a:",j)
}
console.log("b:",j)
// a: 0
// a: 1
// a: 2
// j is not defined

当 JS 解释器在遇到非匿名的立即执行函数时,会创建一个辅助的特定对象,然后将函数名称作为这个对象的属性,因此函数内部才可以访问到当前的函数(只读),而不会指向全局的变量。但在全局中,访问不到该函数。

var foo = 1
(function foo() {
    foo = 10
    console.log(foo)
}()) // -> ƒ foo() { foo = 10 ; console.log(foo) }

console.log(foo) //1

小结:

函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部,而申明的值不会提升

var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用

var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会

let 和 const 作用基本一致,但是后者声明的变量不能再次赋值

var只能提升变量或函数的申明,无法提升申明时的赋值。

var会忽略同一个变量声明,在一个作用域中,如果已经有一个变量使用var声明了,那JS会忽略后续的同一个变量声明。但是赋值还是继续有效。

更多容易出错的题目:

关于变量提升和作用域相关的题目

3.为什么要使用模块化?都有哪几种方式,有什么特点?

前端模块化时将复杂的js文件分为多个独立的模块。用于重用和可维护。这样会引来模块之间相互依赖的问题。所以有了commonJS规范,AMD,CMD等规范,以及打包工具webpack,gulp等。

使用模块化可以给我们带来以下好处

  1. 解决命名冲突和作用域污染
  2. 提供可复用性
  3. 提高代码可维护性

在早期,使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污 染全局作用域的问题。

早期的模块化是AMD 和 CMD技术。但现在基本不用了。像以前的requireJS 是AMD方 法。Sea.js是CMD技术。

AMD,异步模块定义(Asynchronous Module Definition),它是依赖前置 (依赖必须一开始就写好)会先尽早地执行(依赖)模块 。换句话说,所有的require都被提前执行(require 可以是全局或局部 )。

AMD规范只定义了一个函数 define,它是全局变量。用法:

defind(id, dependencies, factory)

CMD(Common Module Definition)更贴近 CommonJS Modules/1.1 和 Node Modules 规范,一个模块就是一个文件;它推崇依赖就近,想什么时候 require就什么时候加载,实现了懒加载(延迟执行 ) ;它也没有全局 require, 每个API都简单纯粹 。

在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:

define(factory);

define(function(require, exports, module) {
    // 模块代码
});

AMD与CMD的比较

  • AMD:依赖前置,预执行(异步加载:依赖先执行)。CMD:依赖就近,懒(延迟)执行(运行到需加载,根据顺序执行)
// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething();
    // 省略1万行
    var b = require('./b') // 依赖可以就近书写
    b.doSomething();
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
    a.doSomething();
    // 省略1万行
    b.doSomething();
})

CommonJS 最早是 Node 在使用,目前也仍然广泛使用。基本语法:

module.exports = {} // 导出全部
exports.a = {} // 部分导出
var a = require(“./a.js”) // 引入

ES Module 是最新的原生实现的模块化方式。与 CommonJS 有以下几个区别:

CommonJS 支持动态路径导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案。

CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响。

CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化。

ES Module 会编译成 require/exports 来执行的。

// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function()

4.Proxy 可以实现什么功能

Vue3.0 通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式(双向绑定)。

let p = new Proxy(target, handler) //target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

与defineProperty 区别:

  1. Proxy 无需递归为每个属性添加代理,一次即可完成所有父子元素的监听,性能更好
  2. 在vue中Object.defineProperty有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,缺陷是浏览器兼容性不好。

优势:

  • 可以劫持整个对象
  • 可以监听对象动态属性的添加
  • 可以监听到数组的变化,包括数组的索引和数组length属性
  • 可以监听删除属性
  • 操作时不是对原对象操作,new Proxy 返回⼀个新对象(它不直接操作对象,而是代理模式,通过对象的代理对象进行操作)。

5.map, filter, reduce 各自有什么作用?

map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的 数组中。map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组。

filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组。 作用:过滤出需要的元素。filter 的回调函数也接受三个参数,用处也相同。

reduce 可以将数组中的元素通过回调函数最终转换为一个值。如计算数组元素的和:

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)

reduce 接受两个参数,分别是回调函数和初始值。

首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入

回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数

在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入

所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6

6.箭头函数和普通函数区别

语法不同:语法简洁,箭头函数省去了function关键字,采用箭头=>来定义函数。函数的参数放在=>前面的括号中,函数体跟在=>后的花括号中。

参数写法不同:

① 如果箭头函数没有参数,直接写一个空括号即可。
② 如果箭头函数的参数只有一个,也可以省去包裹参数的括号。
③ 如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可。

函数体不同:

①如果箭头函数的函数体只有一句代码,就是简单返回某个变量或者返回一个简单的JS表达式,可以省去函数体的大括号{ }和return关键字。

let f = val => val;// 等同于let f = function (val) { return val };
let sum = (num1, num2) => num1 + num2;// 等同于let sum = function(num1, num2) {
return num1 + num2;};

②如果箭头函数的函数体只有一句代码,就是返回一个对象,可以像下面这样写:

// 用小括号包裹要返回的对象,不报错
let getTempItem = id => ({ id: id, name: "Temp" });

③如果箭头函数的函数体只有一条语句并且不需要返回值(最常见是调用一个函数),可以给这条语句前面加一个void关键字,代表无返回值。

let fn = () => void doesNotReturn();

箭头函数最常见的用处就是简化回调函数。

[1,2,3].map(x => x * x);// map箭头函数写法
var result = [2, 5, 1, 4, 3].sort((a, b) => a - b);// 排序箭头函数写法

箭头函数不会创建自己的this

没有自己的this,它只会从自己的作用域链的上一层继承this。它会捕获自己在定义时(注 意,是定义时,不是调用时)所处的外层执行环境的this,并继承这个this值。所以,箭头函数 中this的指向在它被定义的时候就已经确定了,之后永远不会改变。

所以在使用时,普通函数需要用到外部的this时要将this赋值给另一个变量传进去,而箭头 函数不需要。直接可以再函数内部使用外部的this。常见使用场景为vue的方法。

普通函数简单调用时this指向函数本身。注意是在调用时才会确定this指向。

普通函数作为对象的方法调用时,this指向它所属的对象。

var id = 'GLOBAL';var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'

箭头函数继承而来的this指向永远不变,永远是定义时外层环境this

.call()/.apply()/.bind()无法改变箭头函数中this的指向

.call()/.apply()/.bind()方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this 定义时就已经确定且永远不会改变。所以使用这些方法永远也改变不了箭头函数this的指向,虽 然这么做代码不会报错。

由于this指向不是自身,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错。

箭头函数没有自己的arguments,在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。可以在箭头函数中使用rest参数(三点运算符)代替arguments对象。

Var a = (...values)=> {
  console.log(values[0]) // 2
  console.log(values[1]) // 5
  console.log(values[2]) // 8
}


箭头函数不会创建this,不能作为构造函数,不能使用new关键字。另外箭头函数没有原型prototype

本文固定连接:https://code.zuifengyun.com/2019/05/1661.html,转载须征得作者授权。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持