在现代 JavaScript
开发中,ECMAScript 6(ES6)
已经成为了开发者们的标配,它引入了许多令人期待的语言特性,使得JavaScript
的编程体验更加强大、灵活和现代化。对于准备面试或提升自身技能的开发者而言,熟练掌握ES6
是至关重要的一环。
本篇面试宝典将深入剖析ES6
中的关键知识点,涵盖了箭头函数、解构赋值、let 和 const、Promise、模板字符串、类与继承、模块化
等重要概念。我们将通过简单易懂的例子、清晰的解释,帮助你更好地理解和掌握 ES6 的精髓,为你在面试时的表现提供有力的支持。
无论你是初学者还是经验丰富的开发者,这份宝典都将为你提供深入浅出的ES6
学习之路,助你在JavaScript
的世界中游刃有余。让我们一同探索ES6
的奥秘,迎接更高级别的JavaScript
开发挑战!
首先,我们来了解提升(hoisting)
的概念。在使用var
声明变量时,存在提升的情况。例如:
console.log(a); // undefined
var a = 1;
这是因为变量声明被提升,实际执行时相当于:
var a;
console.log(a); // undefined
a = 1;
对于let
和 const
,它们引入了块级作用域,解决了var
的一些问题。使用let
和const
声明的变量不会在声明前被访问,存在暂时性死区。例如:
console.log(b); // ReferenceError: b is not defined
let b = 2;
另外,let
和const
不会像var
一样将变量挂载到全局对象上,而是在块级作用域内有效。同时,使用const
声明的变量不能再次赋值。
var
、let
及 const
区别?在JavaScript
中,虽然有class
语法,但本质上并不存在类,class
只是语法糖,其背后仍然是基于原型的继承。
组合继承是最常用的继承方式:
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function() {
console.log(this.val);
};
function Child(value) {
Parent.call(this, value);
}
Child.prototype = new Parent();
const child = new Child(1);
child.getValue(); // 1
console.log(child instanceof Parent); // true
组合继承通过在子类构造函数中调用父类构造函数,并通过改变子类的原型为父类的实例来实现继承。但这样会导致子类原型上多了不需要的父类属性,存在内存上的浪费。
ES6
引入了class
语法,使得继承更加直观和易用:
class Parent {
constructor(value) {
this.val = value;
}
getValue() {
console.log(this.val);
}
}
class Child extends Parent {
constructor(value) {
super(value);
}
}
const child = new Child(1);
child.getValue(); // 1
console.log(child instanceof Parent); // true
class
实际上是在原型基础上的一层封装,它使得继承更加直观。
Class
如何实现继承?Class
本质是什么?使用模块化可以解决命名冲突、提高代码复用性和可维护性的问题。在早期,立即执行函数是实现模块化的一种常见方式:
(function(globalVariable){
globalVariable.test = function() {}
// ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable);
此外,AMD
和 CMD
也是模块化的方案,而在Node.js
中,CommonJS
是常见的模块管理方式:
// CommonJS
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
console.log(module.a); // log 1
ES6
引入了原生的模块化方案:
// ES Module
// a.js
export const a = 1;
// b.js
import { a } from './a.js';
console.log(a); // 1
涉及面试题:
在早期的JavaScript
中,使用立即执行函数实现模块化是一种常见的手段。通过函数作用域,解决了命名冲突和全局污染的问题:
(function(globalVariable){
globalVariable.test = function() {}
// ... 声明各种变量、函数都不会污染全局作用域
})(globalVariable);
虽然 AMD(Asynchronous Module Definition)
和 CMD(Common Module Definition)
已经相对较少使用,但了解它们的特性也是必要的。它们是用于浏览器端模块化的方案。
// AMD
define(['./a', './b'], function(a, b) {
// 加载模块完毕可以使用
a.do();
b.do();
});
// CMD
define(function(require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require('./a');
a.doSomething();
});
ES6
引入的Proxy
对象用于创建一个对象的代理,从而实现对对象的拦截和自定义行为。Proxy
可以用于实现许多高级特性,如数据响应式。以下是一个简单的例子:
let handler = {
get: function(target, property, receiver) {
console.log(`Getting ${property}`);
return Reflect.get(target, property, receiver);
},
set: function(target, property, value, receiver) {
console.log(`Setting ${property} = ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
let obj = new Proxy({}, handler);
obj.name = "John"; // Setting name = John
console.log(obj.name); // Getting name
Proxy
可以捕获对象的各种操作,如读取属性、设置属性等,为开发者提供了更多的控制力。
这三个数组方法是在ES5
中引入的,但在ES6
中得到了进一步的强化和普及。
map
方法创建一个新数组,其结果是对原数组中的每个元素调用提供的函数进行处理。
let array = [1, 2, 3];
let mappedArray = array.map(value => value * 2);
console.log(mappedArray); // [2, 4, 6]
filter
方法创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。
let array = [1, 2, 4, 6];
let newArray = array.filter(item => item !== 6);
console.log(newArray); // [1, 2, 4]
reduce
方法用于将数组中的元素通过提供的函数逐个累积为单个值。
const arr = [1, 2, 3];
const sum = arr.reduce((acc, current) => acc + current, 0);
console.log(sum); // 6
这三者都是用于存储键值对的容器,但在使用场景和特性上存在一些差异。
Object
的键只能为字符串或者符号。
Object
是无序的,属性的顺序不受保证。
Object
的键值对是显式的,可以通过Object.keys()
获取所有键。
Map
的键可以是任意类型。
Map
是有序的,键值对的顺序与插入顺序一致。
Map
是可迭代的,可以直接进行遍历。
let map = new Map();
map.set("a", 1);
map.set("b", 2);
for (let [key, value] of map) {
console.log(key, value);
}
WeakMap
的键只能是对象。
WeakMap
是弱引用,不会阻止被引用对象被垃圾回收。
WeakMap
没有迭代方法,不能直接遍历。
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "some value");
面试宝典之ES6知识点