if (food) {
var snack = ‘Friskies’;
return snack;
}
return snack;
}
getFood(false); // undefined
然而, 我们把var
替换成let
,观察会发生什么:
let snack = ‘Meow Mix’;
function getFood(food) {
if (food) {
let snack = ‘Friskies’;
return snack;
}
return snack;
}
getFood(false); // ‘Meow Mix’
这种行为变化提示我们,在使用var
重构遗留代码的时候需要小心,盲目的把let
替换成var
会导致以外的行为
注意:
let
和const
具有块作用域。因此在它们定义前调用会引发ReferenceError
console.log(x); // ReferenceError: x is not defined
let x = ‘hi’;
建议:在遗留代码中的保留var
声明表示需要小心的重构。在新建代码库时,使用let
声明以后会只会发生改变的变量,用const
声明那些以后不会被改变的变量。
译注:const
修饰的基本类型不能改变,而其修饰的对象、函数、数组等引用类型,可以改变内部的值,但不能改变其引用。
用块(Blocks)替换立即执行函数(IIFEs)
立即执行函数常被用作闭包,把变量控制在作用域内。在ES6中,我们可以创建一个块作用域而且不仅仅是基于函数的作用域。
(function () {
var food = ‘Meow Mix’;
}());
console.log(food); // Reference Error
Using ES6 Blocks:
{
let food = ‘Meow Mix’;
};
console.log(food); // Reference Error
箭头函数(Arrow Functions)
通常我们会建立嵌套函数,当我们想保留this
上下文作用域的时候(该怎么办?)。如下就是一个例子:
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character; // Cannot read property ‘name’ of undefined
});
};
一个常见的解决方法是利用一个变量存储这个this
的上下文。
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
var that = this; // Store the context of this
return arr.map(function (character) {
return that.name + character;
});
};
我们也可以传入这个this
的上下文:
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character;
}, this);
};
还可以用bind
绑定这个上下文:
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(function (character) {
return this.name + character;
}.bind(this));
};
使用箭头函数,this
的词法作用域不会被影响,我们可以像以下这样重写之前的代码
function Person(name) {
this.name = name;
}
Person.prototype.prefixName = function (arr) {
return arr.map(character => this.name + character);
};
建议: 只要当你想保留this
的词法作用域时,就使用箭头函数
对于一个简单返回一个值的函数来说,箭头函数显得更加简洁
var squares = arr.map(function (x) { return x * x }); // Function Expression
const arr = [1, 2, 3, 4, 5];
const squares = arr.map(x => x * x); // Arrow Function for terser implementation
建议: 尽可能用箭头函数替换你的函数定义
字符串(Strings)
在ES6中, 标准库也在不断的扩充。在这些变化中就有很多方法可以用于字符串,比如.includes()
和.repeat()
.includes()
var string = ‘food’;
var substring = ‘foo’;
console.log(string.indexOf(substring) > -1);
检测返回值是否大于-1表示字符串是否存在,我们可以用.includes()
替换,它返回一个boolean值。
const string = ‘food’;
const substring = ‘foo’;
console.log(string.includes(substring)); // true
.repeat()
function repeat(string, count) {
var strings = [];
while(strings.length < count) {
strings.push(string);
}
return strings.join(‘’);
}
在ES6中,我们一种更简洁的实现方法:
// String.repeat(numberOfRepetitions)
‘meow’.repeat(3); // ‘meowmeowmeow’
模板字面量(Template Literals)
(译注:原文是Template Literals,而非Template Strings)
利用模板字面量,我们可以直接在字符串使用特殊字符而不用转义它们。
var text = “This string contains “double quotes” which are escaped.”;
let text = This string contains "double quotes" which don't need to be escaped anymore.
;
模板字面量还支持插值,可以轻松完成连接字符串和值的任务:
var name = ‘Tiger’;
var age = 13;
console.log(‘My cat is named ’ + name + ’ and is ’ + age + ’ years old.’);
const name = ‘Tiger’;
const age = 13;
console.log(My cat is named ${name} and is ${age} years old.
);
在ES5中,我们这样操作多行字符串:
var text = (
‘cat\n’ +
‘dog\n’ +
‘nickelodeon’
);
或者
var text = [
‘cat’,
‘dog’,
‘nickelodeon’
].join(‘\n’);
模板字面量可以保留多行字符串,我们无需显式的放置它们:
let text = ( `cat
dog
nickelodeon`
);
模板字面量可以接受表达式, 比如:
let today = new Date();
let text = The time and date is ${today.toLocaleString()}
;
解构赋值(Destructuring)
解构赋值允许我们从数组或对象中提取出值(甚至深度嵌套的值),并把他们存入变量的简单语法
解构数组(Destructuring Arrays)
var arr = [1, 2, 3, 4];
var a = arr[0];
var b = arr[1];
var c = arr[2];
var d = arr[3];
let [a, b, c, d] = [1, 2, 3, 4];
console.log(a); // 1
console.log(b); // 2
解构对象(Destructuring Objects)
var luke = { occupation: ‘jedi’, father: ‘anakin’ };
var occupation = luke.occupation; // ‘jedi’
var father = luke.father; // ‘anakin’
let luke = { occupation: ‘jedi’, father: ‘anakin’ };
let {occupation, father} = luke;
console.log(occupation); // ‘jedi’
console.log(father); // ‘anakin’
模块(Modules)
ES6之前,我们只用如Browserify的库在客户端创建模块,并且需要用到Node.js。利用ES6,我们现在可以直接使用任何类型的模块(AMD和CommonJS)
CommonJS中的exports
module.exports = 1;
module.exports = { foo: ‘bar’ };
module.exports = [‘foo’, ‘bar’];
module.exports = function bar () {};
ES6中的export
在ES6中,提供各种不同类型的exports,我们可以运行如下:
export let name = ‘David’;
export let age = 25;
输出对象列表:
function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
export { sumTwo, sumThree };
我们也可以简单地通过export
关键字输出函数、对象和值(等等):
export function sumTwo(a, b) {
return a + b;
}
export function sumThree(a, b, c) {
return a + b + c;
}
And lastly, we can export default bindings:
function sumTwo(a, b) {
return a + b;
}
function sumThree(a, b, c) {
return a + b + c;
}
let api = {
sumTwo,
sumThree
};
输出默认api:
/* Which is the same as
- export { api as default };
*/
建议:在模块结束的地方,始终输出默认的方法。这样可以清晰地看到接口,并且通过弄清楚输出值的名称节省时间。所以在CommonJS模块中通常输出一个对象或值。坚持使用这种模式,会使我们的代码易读,并且可以轻松的在ES6和CommonJS中进行插补。
ES6
ES6 提供提供各种不同的imports,我们输入一整个文件:
import ‘underscore’;
这里值得注意的是,简单的输入一文件会在文件的最顶层执行代码。和Python类似,我们已经命名了imports:
import { sumTwo, sumThree } from ‘math/addition’;
我们还可以重命名这些已经有名的imports:
import {
sumTwo as addTwoNumbers,
sumThree as sumThreeNumbers
} from ‘math/addition’;
此外,我们可以输入各种东西(也叫做 namespace import)
import * as util from ‘math/addition’;
最后,我们可以从模块输入一列值:
import * as additionUtil from ‘math/addition’;
const { sumTwo, sumThree } = additionUtil;
如下这样从默认绑定进行输入
import api from ‘math/addition’;
// 例如: import { default as api } from ‘math/addition’;
虽然最好要保持输出简单,但是如果我们需要,我们有时可以混用默认输入,当我们想如下输出的时候:
// foos.js
export { foo as default, foo1, foo2 };
我们可以如下输入它们:
import foo, { foo1, foo2 } from ‘foos’;
当使用commonj语法(如React)输入一个模型的输出时,我们可以这样做:
import React from ‘react’;
const { Component, PropTypes } = React;
这个也可以进一步简化,使用:
import React, { Component, PropTypes } from ‘react’;
注意:输出的值是绑定,不是引用。因此,绑定的值发生变化会影响输出的模型中的值。避免修改这些输出值的公共接口。
参数(Parameters)
在ES5中,我们可以很多方法操作函数参数的默认值、未定义的参数和有定义的参数。在ES6中,我们可以用更简单的语法实现这一切。
默认参数(Default Parameters)
function addTwoNumbers(x, y) {
x = x || 0;
y = y || 0;
return x + y;
}
在ES6中,我们可以简单的把默认值赋给参数:
function addTwoNumbers(x=0, y=0) {
return x + y;
}
addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0
剩余参数(Rest Parameters)
在ES5中,我们这样操作一个未定义的参数:
function logArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
使用休止符(…)我们可以传入大量未定义的参数:
function logArguments(…args) {
for (let arg of args) {
console.log(arg);
}
}
已命名的参数(Named Parameters)
ES5中,一种处理已命名参数的方式是使用选项方式,这种方法来自jQuery。
function initializeCanvas(options) {
var height = options.height || 600;
var width = options.width || 400;
var lineStroke = options.lineStroke || ‘black’;
}
通过解构成正式参数的方式,我们可以实现同样的功能:
function initializeCanvas(
{ height=600, width=400, lineStroke=‘black’}) {
// Use variables height, width, lineStroke here
}
如果我们想使全部参数值可选,我们可以用一个空对象这样结构:
function initializeCanvas(
{ height=600, width=400, lineStroke=‘black’} = {}) {
// …
}
展开运算符(Spread Operator)
在ES5中,查找一个array中的最大值需要使用Math.max的apply方法:
Math.max.apply(null, [-1, 100, 9001, -32]); // 9001
在es6中,我们使用展开运算符将array传递给函数作为参数:
Math.max(…[-1, 100, 9001, -32]); // 9001
我们可以用这样简洁的语法链接数组字面量:
let cities = [‘San Francisco’, ‘Los Angeles’];
let places = [‘Miami’, …cities, ‘Chicago’]; // [‘Miami’, ‘San Francisco’, ‘Los Angeles’, ‘Chicago’]
类(classes)
在ES6之前,我们通过创建构造器函数,并且在其prototype上添加属性的方法创建一个类:
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.incrementAge = function () {
return this.age += 1;
};
并且用一下方法创建继承类:
function Personal(name, age, gender, occupation, hobby) {
Person.call(this, name, age, gender);
this.occupation = occupation;
this.hobby = hobby;
}
Personal.prototype = Object.create(Person.prototype);
Personal.prototype.constructor = Personal;
Personal.prototype.incrementAge = function () {
Person.prototype.incrementAge.call(this);
this.age += 20;
console.log(this.age);
};
ES6 提供了十分有用的句法在后台实现这一切,我们可以这样直接创建一个类:
class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
incrementAge() {
this.age += 1;
}
}
并且用关键字extends
实现继承:
class Personal extends Person {
constructor(name, age, gender, occupation, hobby) {
super(name, age, gender);
this.occupation = occupation;
this.hobby = hobby;
}
incrementAge() {
super.incrementAge();
this.age += 20;
console.log(this.age);
}
}
建议:使用ES6的语法创建类模糊了后台的实现和原型如何工作,这个好特性可以使我们的代码更整洁。
Symbols
Symbol在ES6之前就已经出现了, 但是现在我们有了一个公共的接口可以直接使用。Symbol是唯一且不可改变的值,被用作哈希中的键。
Symbol()
调用Symbol()
或者Symbol(description)
会创建一个不能在全局查找的独一无二的符号。一种使用symbol()
的情况是,利用自己的逻辑修补第三方的对象或命名空间,但不确定会不会在库更新时产生冲突。例如,如果你想添加一个方法refreshCompontent
到React.Component
,并且确信这个方法他们不会在以后的更新中添加。
const refreshComponent = Symbol();
React.Component.prototype[refreshComponent] = () => {
// do something
}
###Symbol.for(key)
Symbol.for(key)
依然会创建一个唯一且不能修改的Symbol,但是它可以在全局被查找。两次调用相同的Symbol.for(key)
会创建一样的Symbol实例。注意,他和Symbol(description)
不是相同的:
Symbol(‘foo’) === Symbol(‘foo’) // false
Symbol.for(‘foo’) === Symbol(‘foo’) // false
Symbol.for(‘foo’) === Symbol.for(‘foo’) // true
一个常见的symbol方法Symbol.for(key)
是可互操作的。(使用这个方法)这个可以通过使用自己的代码在包括已知接口的第三方的对象参数中查找symbol成员实现,例如:
function reader(obj) {
const specialRead = Symbol.for(‘specialRead’);
if (obj[specialRead]) {
const reader = objspecialRead;
// do something with reader
} else {
throw new TypeError(‘object cannot be read’);
}
}
在另一个库中:
const specialRead = Symbol.for(‘specialRead’);
class SomeReadableType {
const reader = createSomeReaderFrom(this);
return reader;
}
}
ES6中,一个值得注意的是关于Symbol的互操作性的例子是Symbol.iterator
,它存在于Arrays、Strings、Generators等等的所有可迭代类型中。当作为一个方法调用的时候,它会返回一个具有迭代器接口的对象。
Maps
Maps是JavaScript中十分有用的结构。在ES6之前, 我们通过对象创建哈希maps:
var map = new Object();
map[key1] = ‘value1’;
map[key2] = ‘value2’;
但是,这样不能保护我们对已有属性以外的重写:
getOwnProperty({ hasOwnProperty: ‘Hah, overwritten’}, ‘Pwned’);
TypeError: Property ‘hasOwnProperty’ is not a function
Map允许我们使用set、get和search(等等)访问属性值。
let map = new Map();
map.set(‘name’, ‘david’);
map.get(‘name’); // david
map.has(‘name’); // true
最意想不到的是Map不再限制我们只使用字符串作为键,我们现在可以使用任何类型作为键而不会发生类型转换。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取。
Maps是JavaScript中十分有用的结构。在ES6之前, 我们通过对象创建哈希maps:
var map = new Object();
map[key1] = ‘value1’;
map[key2] = ‘value2’;
但是,这样不能保护我们对已有属性以外的重写:
getOwnProperty({ hasOwnProperty: ‘Hah, overwritten’}, ‘Pwned’);
TypeError: Property ‘hasOwnProperty’ is not a function
Map允许我们使用set、get和search(等等)访问属性值。
let map = new Map();
map.set(‘name’, ‘david’);
map.get(‘name’); // david
map.has(‘name’); // true
最意想不到的是Map不再限制我们只使用字符串作为键,我们现在可以使用任何类型作为键而不会发生类型转换。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-HW4iuxCQ-1712097072279)]
[外链图片转存中…(img-299cTSiR-1712097072279)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-FhM9WXUz-1712097072280)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
[外链图片转存中…(img-bcj5wRmb-1712097072280)]
[外链图片转存中…(img-XufIJE3y-1712097072280)]
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取。