背景
马上又到年底了,跳槽的季节。
我又想起来曾经准备面试
的情景, 各种搜集资料, 整理, 面试, 再整理, 十分的辛苦。
其实,无论面试哪家公司, 基础
都是免不了的.
之前就有整理一下这些资料的想法,不过自己比较懒, 一只没有动手。
最近在做公众号,就想着干脆搞一搞,把这些基础知识整理一下,以后自己也能看。
刚好国庆在家看了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 变量的三个步骤
:
创建
初始化
赋值
为了便于理解, 我们先看看var
的 创建、初始化和赋值
过程:
function foo(){
var x = 1
var y = 2
}
foo()
执行foo 时, 会有一些过程(部分过程):
- 进入 foo,为 foo 创建一个环境。
- 找到 foo 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
- 将这些变量「初始化」为 undefined。
- 执行代码
- x = 1 将 x 变量「赋值」为 1
- y = 2 将 y 变量「赋值」为 2
也就是说 va
声明, 会在代码执行之前就将 创建变量
,并将其初始化为 undefined
。
这就解释了为什么在 var x = 1 之前 console.log(x) 会得到 undefined
。
接下来看 let 声明的「创建、初始化和赋值」过程
// ...
{
let x = 1;
x = 2
}
我们看一下过程:
- 找到所有用
let
声明的变量,在环境中创建
这些变量 - 执行代码(注意现在还没有初始化)
- 执行 x = 1,将 x 「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
- 执行 x = 2,对 x 进行「赋值」
这就解释了为什么在 let x 之前使用 x 会报错:
let x = 'global'
{
console.log(x) // Uncaught ReferenceError: x is not defined
let x = 1
}
原因有两个
- console.log(x) 中的 x 指的是下面的 x,而不是全局的 x.
- 执行 log 时 x 还没「初始化」,所以不能使用(也就是所谓的
TDZ
, tempory dead zone, 暂时死区)
说到这里, 就都清楚了:
- let 的「创建」过程被提升了,但是初始化没有提升。
- var 的「创建」和「初始化」都被提升了。
function 也是类似的,而且function 的「创建」「初始化」和「赋值」都被提升了。
这一点, 感兴趣的朋友可以自己实验一下。
算了, 直接给个例子吧:
JS 引擎会有一下过程:
- 找到所有用 function 声明的变量,在环境中「创建」这些变量。
- 将这些变量「初始化」并「赋值」为
function(){ console.log(2) }
。 - 开始执行代码 fn2()`
也就是说 function 声明会在代码执行之前就「创建、初始化并赋值」。
这里做一下简单的总结:
-
函数提升优先于变量提升
.函数提升
会把整个函数挪到作用域顶部
,变量提升
只会把声明
挪到作用域顶部
。 -
var
存在提升
,我们能在声明之前
使用。 -
let
,const
因为存在暂时性死区
,不能在声明前使用
。 -
var
在全局作用域
下声明变量, 会导致变量挂载在window
上, 而另外两者不会
。 -
let
和const
的作用基本一致,但是后者声明的变量不能再次赋值。
箭头函数
这个也是 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})
模块化
模块化的好处是十分明显的:
解决命名冲突
提供复用性
提高代码可维护性
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进阶 」,一起学习成长
可以通过公众号菜单栏的联系我
, 加入我们的微信群
, 一起愉快的玩耍。