乐趣区

掌握前端面试基础系列一-ES6

背景


马上又到年底了,跳槽的季节。

我又想起来曾经准备 面试 的情景,各种搜集资料,整理,面试,再整理,十分的辛苦。

其实,无论面试哪家公司,基础 都是免不了的.

之前就有整理一下这些资料的想法,不过自己比较懒,一只没有动手。

最近在做公众号,就想着干脆搞一搞,把这些基础知识整理一下,以后自己也能看。

刚好国庆在家看了 ES6 相关的东西,这一篇就从 ES6 开始吧。

正文


var, let, const

这三个东西,经常会被问到。

汇总一下,基本上就是:

  • var, let, const 有什么 区别?
  • let, const 有没有 变量提升(hosting) ?
  • 什么是 TDZ ?

首先,我们先整体的看下 区别:

针对这几点,我们一个个看。

首先,我们还是先了解一下 变量提升.

看个例子:

console.log(a) // undefined
var a = 1

这里我们可以看到,第一行中的 a 虽然还没声明,但是我们用起来却不会报错。这种情况,就是声明的提升。

其实也就是:

var a
console.log(a) // undefined
a = 1

但是,如果是换成let,情况就不一样了:

要理解这个现象,首先需要搞清楚提升的本质,理解创建 javascript 变量的 三个步骤

  1. 创建
  2. 初始化
  3. 赋值

为了便于理解,我们先看看 var 创建、初始化和赋值 过程:


function foo(){
  var x = 1
  var y = 2
}

foo()

执行 foo 时,会有一些过程(部分过程):

  1. 进入 foo,为 foo 创建一个环境。
  2. 找到 foo 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
  3. 将这些变量「初始化」为 undefined。
  4. 执行代码
  5. x = 1 将 x 变量「赋值」为 1
  6. y = 2 将 y 变量「赋值」为 2

也就是说 va 声明, 会在代码执行之前就将 创建变量,并将其初始化为 undefined

这就解释了为什么在 var x = 1 之前 console.log(x) 会得到 undefined

接下来看 let 声明的「创建、初始化和赋值」过程

// ...
{
  let x = 1;
  x = 2
}

我们看一下过程:

  1. 找到所有用 let 声明的变量,在环境中 创建 这些变量
  2. 执行代码(注意现在还没有初始化)
  3. 执行 x = 1,将 x「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
  4. 执行 x = 2,对 x 进行「赋值」

这就解释了为什么在 let x 之前使用 x 会报错:

let x = 'global'
{console.log(x) // Uncaught ReferenceError: x is not defined
  let x = 1
}

原因有两个

  1. console.log(x) 中的 x 指的是下面的 x,而不是全局的 x.
  2. 执行 log 时 x 还没「初始化」,所以不能使用(也就是所谓的 TDZ, tempory dead zone, 暂时死区)

说到这里,就都清楚了:

  1. let 的「创建」过程被提升了,但是初始化没有提升。
  2. var 的「创建」和「初始化」都被提升了。

function 也是类似的,而且 function 的「创建」「初始化」和「赋值」都被提升了。

这一点,感兴趣的朋友可以自己实验一下。

算了,直接给个例子吧:

<script>
bar()
 
function  bar(){console.log(2)
}
</script>

JS 引擎会有一下过程:

  1. 找到所有用 function 声明的变量,在环境中「创建」这些变量。
  2. 将这些变量「初始化」并「赋值」为 function(){ console.log(2) }
  3. 开始执行代码 fn2()`

也就是说 function 声明会在代码执行之前就「创建、初始化并赋值」。

这里做一下简单的总结:

  1. 函数提升优先于变量提升 . 函数提升 会把整个函数挪到 作用域顶部 变量提升 只会把 声明 挪到 作用域顶部
  2. var存在 提升 ,我们能在声明 之前 使用。
  3. let, const 因为存在 暂时性死区 不能在声明前使用
  4. var 全局作用域 下声明变量, 会导致变量挂载在 window 上,而另外两者 不会
  5. letconst的作用基本一致,但是后者声明的变量不能再次赋值。

箭头函数

这个也是 ES6 里比较好用的 feature, 我们每天也都会用到。

箭头函数是 ES6 中新的函数定义形式:

function name(arg1, arg2) {}

// 可以使用

(arg1, arg2) => {}

// 来定义。

箭头函数,一方面看起来比较简洁,另一方面,之解决 ES5 时代,this的问题.

看个例子:


function fn() {console.log(this)  // {a: 100},该作用域下的 this 的真实的值
    var arr = [1, 2, 3]
    
    // ES5
    arr.map(function (item) {console.log(this)  // window
    })
    
    // 箭头函数
    arr.map(item => {console.log(this)  // {a: 100} 这里打印的就是父作用域的 this
    })
}
fn.call({a: 100})

模块化

模块化的好处是十分明显的:

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

ES6 之前也有模块化的方案:AMD, CMD,就简单的提一下,不是本文讨论的主要内容。

AMD, CMD

// AMD
define(['./a', './b'], function(a, b) {a.do()
  b.do()})

// CMD
define(function(require, exports, module) {var a = require('./a')
  a.doSomething()})

IIFE

也有一种 IIFE 的 形式:

(function(globalVariable){
   // 形成一个独立的作用域,不会污染全局
   // ...
})(globalVariable)

CommonJS

CommonJS 最早是 Node.js 在使用,目前也仍然广泛使用。

看个例子:

// a.js

module.exports = {a: 1}

// b.js
var module = require('./a.js')
module.a // 1

ES6 Module

ES Module 是原生实现的模块化方案, 提供了一种新的,可以文件作为模块的开发方式。

使用方式就是我们常见的:

// a.js
export function a() {}
export default function() {}


//b.js

import XXX from './a.js'
import {XXX} from './a.js'

如果只是输出一个唯一的对象,使用 export default 即可:


// util1.js
export default {a: 100}

// index.js 文件
import obj from './util1.js'
console.log(obj) // {a: 100}

如果想要输出许多个对象,就不能用 default 了,而且 import 时候要加 {…},代码如下:


// foo.js
export function fn1() {alert('fn1')
}

export function fn2() {alert('fn2')
}

// index.js
import {fn1, fn2} from './foo.js'
fn1()
fn2()

Class

class 其实一直是 js 的保留字,直到 ES6 才正式用到。

(js 中并不存在类,class 只是个语法糖。本质上还是函数, 其实就是要模拟面向对象的语法,你懂的)

ES6 的 class 就是取代之前 构造函数 初始化对象的形式,从 语法 上更加接近 面向对象 的写法。

例如:

// ES5
function MathHandle(x, y) {
  this.x = x;
  this.y = y;
}

MathHandle.prototype.add = function () {return this.x + this.y;};

var method = new MathHandle(1, 2);
console.log(method.add())

ES6 class 的写法:

class MathHandle {constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  add() {return this.x + this.y;}
}
const method = new MathHandle(1, 2);
console.log(method.add())

注意以下几点:

  • class 是一种新的语法形式,是class Name {...} 这种形式,和函数的写法完全不一样.
  • 两者对比,构造函数函数体的内容要放在 class 中的 constructor 函数中,constructor即构造器,初始化实例时默认执行.
  • class 中函数的写法是 add() {...} 这种形式,并没有 function 关键字.

原型继承和 Class 继承

使用 Class 来实现继承就更加简单了,至少比构造函数实现继承简单很多:


// 动物
function Animal() {this.eat = function () {console.log('animal eat')
    }
}
// 狗
function Dog() {this.bark = function () {console.log('dog bark')
    }
}

Dog.prototype = new Animal()


var husky = new Dog()

husky.bark() // dog bark

ES6 写法:

class Animal {constructor(name) {this.name = name}
    eat() {console.log(`${this.name} eat`)
    }
}

class Dog extends Animal {constructor(name) {super(name)
        this.name = name
    }
    
    bark() {console.log(`${this.name} say`)
    }
}

const husky = new Dog('哈士奇')
husky.bark()

注意以下两点:

  • 使用 extends 即可实现继承,更加符合经典面向对象语言的语法.
  • 子类的 constructor 一定要执行super(),调用父类的constructor.

以上这几个方面都是面试中经常问的,ES6 的内容远远不知这些,好用的特性还有很多,比如:

  • ...」操作符
  • 解构
  • Set
  • Map
  • 等等

这里就不一一介绍了。

...」操作符 这个可以参考我的这篇文章:

深入了解强大的 ES6「…」运算符

ES6 面试问的 比较多的 大概就是以上这几点,可能有所纰漏,后面再做补充吧。

希望对大家有所帮助。

最后

今天是楼主节后第一天上班,不在状态。

说个最尴尬的事情吧,今天要填 2019 的绩效自评,其中有一项:

公司在哪些方面能帮助你成长?

本来是一个很正常的问题:

我是这么写的:

写完笑了笑,跟同事调侃了一下,可不得加薪吗。说完回头我就直接点了提交。

提交完感觉哪里不大对?

就这么提交了???

,害怕,卧,我干了什么

反正也不能改了,就这样吧。

文中若有纰漏,还请各位大佬指正。

最后的最后

如果你觉得内容有帮助可以关注下我的公众号「前端 e 进阶」,一起学习成长

可以通过公众号菜单栏的 联系我 ,加入我们的 微信群,一起愉快的玩耍。

退出移动版