都说金九银十是一个面试的好季节,最近在
GitHub
上看到了一些关于前端的面试题,也比较基础,在这里整理了一下,由于内容较多,在这里分为HTML
、CSS
、JavaScript
三篇,但愿能够对你们有所帮助,趁着“好季节”,找到本身心仪的工做,固然也包括我本身在内,你们一块儿加油哈!javascript
由于掘金的某条沸点,在这里先说明一下,本篇文章适合目前正在找工做或者以为本身的基础不太扎实的小伙伴,大神就跳过吧O(∩_∩)O。前端
转发自 github.com/yangshun/fr…java
事件委托是将事件监听器添加到父元素,而不是每一个子元素单独设置事件监听器。当触发子元素时,事件会冒泡到父元素,监听器就会触发。这种技术的好处是:git
1.内存占用减小,由于只须要一个父元素的事件处理程序,而没必要为每一个后代都添加事件处理程序。
2.无需从已删除的元素中解绑处理程序,也无需将处理程序绑定到新元素上。github
JavaScript
中的this
。JS 中的 this 是一个相对复杂的概念,不是简单几句能解释清楚的。粗略地讲,函数的调用方式决定了 this 的值。我阅读了网上不少关于 this 的文章,Arnav Aggrawal 写的比较清楚。 this 取值符合如下规则:面试
1.在调用函数时使用
new
关键字,函数内的this
是一个全新的对象。
2.若是apply
、call
或bind
方法用于调用、建立一个函数,函数内的this
就是做为参数传入这些方法的对象。
3.当函数做为对象里的方法被调用时,函数内的this
是调用该函数的对象。好比当obj.method()
被调用时,函数内的this
将绑定到obj
对象。
4.若是调用函数不符合上述规则,那么this
的值指向全局对象(global object)
。浏览器环境下this
的值指向window
对象,可是在严格模式下('use strict')
,this
的值为undefined
。
5.若是符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定this
的值。
6.若是该函数是ES2015
中的箭头函数,将忽略上面的全部规则,this
被设置为它被建立时的上下文。数据库
这是一个很是常见的 JavaScript
问题。全部 JS
对象都有一个prototype
属性,指向它的原型对象。当试图访问一个对象的属性时,若是没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。这种行为是在模拟经典的继承,可是与其说是继承,不如说是委托。express
AMD
和CommonJS
的了解。它们都是实现模块体系的方式,直到ES2015
出现以前,JavaScript
一直没有模块体系。CommonJS
是同步的,而 AMD(Asynchronous Module Definition)
从全称中能够明显看出是异步的。CommonJS
的设计是为服务器端开发考虑的,而AMD
支持异步加载模块,更适合浏览器。编程
我发现AMD
的语法很是冗长,CommonJS
更接近其余语言import
声明语句的用法习惯。大多数状况下,我认为AMD
没有使用的必要,由于若是把全部 JavaScript
都捆绑进一个文件中,将没法获得异步加载的好处。此外,CommonJS 语法上更接近 Node 编写模块的风格,在先后端都使用 JavaScript
开发之间进行切换时,语境的切换开销较小。后端
我很高兴看到ES2015
的模块加载方案同时支持同步和异步,咱们终于能够只使用一种方案了。虽然它还没有在浏览器和Node
中彻底推出,可是咱们可使用代码转换工具进行转换。
IIFE:function foo(){ }();
,须要做出哪些修改才能使其成为IIFE
?IIFE(Immediately Invoked Function Expressions)
表明当即执行函数。 JavaScript
解析器将 function foo(){ }();
解析成 function foo(){ }和();
。其中,前者是函数声明;后者(一对括号)是试图调用一个函数,却没有指定名称,所以它会抛出Uncaught SyntaxError: Unexpected token )
的错误。
修改方法是:再添加一对括号,形式上有两种:(function foo(){ })()
和(function foo(){ }())
。以上函数不会暴露到全局做用域,若是不须要在函数内部引用自身,能够省略函数的名称。
你可能会用到void
操做符:void function foo(){ }();
。可是,这种作法是有问题的。表达式的值是undefined
,因此若是你的IIFE
有返回值,不要用这种作法。例如:
const foo = void function bar() { return 'foo'; }();
console.log(foo); // undefined
复制代码
null
、undefined
和未声明变量之间有什么区别?如何检查判断这些状态值?当你没有提早使用var
、let
或const
声明变量,就为一个变量赋值时,该变量是未声明变量(undeclared variables)
。未声明变量会脱离当前做用域,成为全局做用域下定义的变量。在严格模式下,给未声明的变量赋值,会抛出ReferenceError
错误。和使用全局变量同样,使用未声明变量也是很是很差的作法,应当尽量避免。要检查判断它们,须要将用到它们的代码放在try/catch
语句中。
function foo() {
x = 1; // 在严格模式下,抛出 ReferenceError 错误
}
foo();
console.log(x); // 1
复制代码
当一个变量已经声明,但没有赋值时,该变量的值是undefined
。若是一个函数的执行结果被赋值给一个变量,可是这个函数却没有返回任何值,那么该变量的值是undefined
。要检查它,须要使用严格相等(===);或者使用typeof
,它会返回 'undefined'
字符串。请注意,不能使用非严格相等(==)来检查,由于若是变量值为null
,使用非严格相等也会返回true
。
var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true
console.log(foo == null); // true. 错误,不要使用非严格相等!
function bar() {}
var baz = bar();
console.log(baz); // undefined
复制代码
null
只能被显式赋值给变量。它表示空值,与被显式赋值 undefined
的意义不一样。要检查判断null
值,须要使用严格相等运算符。请注意,和前面同样,不能使用非严格相等(==)来检查,由于若是变量值为undefined
,使用非严格相等也会返回true
。
var foo = null;
console.log(foo === null); // true
console.log(foo == undefined); // true. 错误,不要使用非严格相等!
复制代码
做为一种我的习惯,我从不使用未声明变量。若是定义了暂时没有用到的变量,我会在声明后明确地给它们赋值为null
。
闭包是函数和声明该函数的词法环境的组合。词法做用域中使用的域,是变量在代码中声明的位置所决定的。闭包是即便被外部函数返回,依然能够访问到外部(封闭)函数做用域的函数。
为何使用闭包?
1.利用闭包实现数据私有化或模拟私有方法,这个方式也称为模块模式。
2.部分参数函数柯里化。
.forEach
循环和.map()
循环的主要区别,它们分别在什么状况下使用?为了理解二者的区别,咱们看看它们分别是作什么的。
1.forEach
遍历数组中的元素。
为每一个元素执行回调。
无返回值。
const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
// 执行与 num、index 相关的代码
});
// doubled = undefined
2.map
遍历数组中的元素
经过对每一个元素调用函数,将每一个元素“映射(map)”到一个新元素,从而建立一个新数组。
const a = [1, 2, 3];
const doubled = a.map(num => {
return num * 2;
});
// doubled = [2, 4, 6]
复制代码
.forEach
和.map()
的主要区别在于.map()
返回一个新的数组。若是你想获得一个结果,但不想改变原始数组,用.map()
。若是你只须要在数组上作迭代修改,用forEach
。
匿名函数能够在IIFE
中使用,来封装局部做用域内的代码,以便其声明的变量不会暴露到全局做用域。
(function() {
// 一些代码。
})();
匿名函数能够做为只用一次,不须要在其余地方使用的回调函数。当处理函数在调用它们的程序内部被定义时,代码具备更好地自闭性和可读性,能够省去寻找该处理函数的函数体位置的麻烦。
setTimeout(function() {
console.log('Hello world!');
}, 1000);
匿名函数能够用于函数式编程或 Lodash(相似于回调函数)。
const arr = [1, 2, 3];
const double = arr.map(function(el) {
return el * 2;
});
console.log(double); // [2, 4, 6]
复制代码
我之前使用 Backbone
组织个人模型 (model)
,Backbone
鼓励采用面向对象的方法——建立 Backbone
模型,并为其添加方法。
模块模式仍然是很好的方式,可是如今我使用基于 React/Redux
的 Flux
体系结构,它鼓励使用单向函数编程的方法。我用普通对象 (plain object)
表示个人 app
模型,编写实用纯函数去操做这些对象。使用动做 (actions)
和化简器 (reducers)
来处理状态,就像其余 Redux
应用同样。
我尽量避免使用经典继承。若是非要这么作,我会坚持这些原则。
(host objects)
和原生对象 (native objects)
的区别是什么?原生对象是由 ECMAScript
规范定义的 JavaScript
内置对象,好比String
、Math
、RegExp
、Object
、Function
等等。
宿主对象是由运行时环境(浏览器或 Node
)提供,好比 window
、XMLHTTPRequest
等等。
function Person(){}
var person = Person()
var person = new Person()
复制代码
这个问题问得很含糊。我猜这是在考察 JavaScript 中的构造函数(constructor)。从技术上讲,function Person(){}只是一个普通的函数声明。使用 PascalCase 方式命名函数做为构造函数,是一个惯例。
var person = Person()将Person以普通函数调用,而不是构造函数。若是该函数是用做构造函数的,那么这种调用方式是一种常见错误。一般状况下,构造函数不会返回任何东西,所以,像普通函数同样调用构造函数,只会返回undefined赋给用做实例的变量。
var person = new Person()使用new操做符,建立Person对象的实例,该实例继承自Person.prototype。另一种方式是使用Object.create
,例如:
Object.create(Person.prototype)`。
function Person(name) {
this.name = name;
}
var person = Person('John');
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined
var person = new Person('John');
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"
复制代码
.call
和 .apply
有什么区别?.call
和.apply
都用于调用函数,第一个参数将用做函数内 this
的值。然而,.call
接受逗号分隔的参数做为后面的参数,而 .apply
接受一个参数数组做为后面的参数。一个简单的记忆方法是,从call
中的 C
联想到逗号分隔(comma-separated),从apply
中的 A
联想到数组(array)。
function add(a, b) {
return a + b;
}
console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3
复制代码
Function.prototype.bind
的用法。摘自MDN
:
bind()
方法建立一个新的函数, 当被调用时,将其 this
关键字设置为提供的值,在调用新函数时,在任何提供以前提供一个给定的参数序列。
根据个人经验,将this
的值绑定到想要传递给其余函数的类的方法中是很是有用的。在 React
组件中常常这样作。
document.write()
?document.write()
用来将一串文本写入由document.open()
打开的文档流中。当页面加载后执行document.write()
时,它将调用document.open
,会清除整个文档(<head>
和<body>
会被移除!),并将文档内容替换成给定的字符串参数。所以它一般被认为是危险的而且容易被误用。
网上有一些答案,解释了document.write()
被用于分析代码中,或者当你想包含只有在启用了 JavaScript
的状况下才能工做的样式。它甚至在 HTML5
样板代码中用于并行加载脚本并保持执行顺序!可是,我怀疑这些使用缘由是过期的,如今能够在不使用document.write()
的状况下实现。若是个人观点有错,请纠正我。
Ajax
。Ajax(asynchronous JavaScript and XML)
是使用客户端上的许多 Web
技术,建立异步 Web
应用的一种 Web
开发技术。借助 Ajax
,Web
应用能够异步(在后台)向服务器发送数据和从服务器检索数据,而不会干扰现有页面的显示和行为。经过将数据交换层与表示层分离,Ajax
容许网页和扩展 Web
应用程序动态更改内容,而无需从新加载整个页面。实际上,如今一般将 XML
替换为 JSON
,由于 JavaScript
对 JSON
有原生支持优点。
XMLHttpRequest API
常常用于异步通讯。此外还有最近流行的fetch API
。
优势
交互性更好。来自服务器的新内容能够动态更改,无需从新加载整个页面。
减小与服务器的链接,由于脚本和样式只须要被请求一次。
状态能够维护在一个页面上。JavaScript 变量和 DOM 状态将获得保持,由于主容器页面未被从新加载。
基本上包括大部分 SPA 的优势。
复制代码
缺点
动态网页很难收藏。
若是 JavaScript 已在浏览器中被禁用,则不起做用。
有些网络爬虫不执行 JavaScript,也不会看到 JavaScript 加载的内容。
基本上包括大部分 SPA 的缺点。
复制代码
JSONP
的工做原理,它为何不是真正的 Ajax
?JSONP
(带填充的 JSON
)是一种一般用于绕过 Web
浏览器中的跨域限制的方法,由于 Ajax
不容许跨域请求。
JSONP
经过<script>
标签发送跨域请求,一般使用callback
查询参数,例如:example.com?callback=printData。 而后服务器将数据包装在一个名为printData
的函数中并将其返回给客户端。
<!-- https://mydomain.com -->
<script>
function printData(data) {
console.log(`My name is ${data.name}!`);
}
</script>
<script src="https://example.com?callback=printData"></script>
printData({ name: 'Yang Shun' });
复制代码
客户端必须在其全局范围内具备printData
函数,而且在收到来自跨域的响应时,该函数将由客户端执行。
JSONP
可能具备一些安全隐患。因为 JSONP
是纯 JavaScript
实现,它能够完成 JavaScript
所能作的一切,所以须要信任 JSONP
数据的提供者。
现现在,跨来源资源共享(CORS
) 是推荐的主流方式,JSONP
已被视为一种比较 hack
的方式。
变量提高(hoisting
)是用于解释代码中变量声明行为的术语。使用var
关键字声明或初始化的变量,会将声明语句“提高”到当前做用域的顶部。 可是,只有声明才会触发提高,赋值语句(若是有的话)将保持原样。咱们用几个例子来解释一下。
// 用 var 声明获得提高
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1
// 用 let/const 声明不会提高
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2
函数声明会使函数体提高,但函数表达式(以声明变量的形式书写)只有变量声明会被提高。
// 函数声明
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}
console.log(foo); // [Function: foo]
// 函数表达式
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function() {
console.log('BARRRR');
};
console.log(bar); // [Function: bar]
复制代码
当一个事件在 DOM
元素上触发时,若是有事件监听器,它将尝试处理该事件,而后事件冒泡到其父级元素,并发生一样的事情。最后直到事件到达祖先元素。事件冒泡是实现事件委托的原理(event delegation
)。
attribute
” 和 “property
” 之间有什么区别?“Attribute
” 是在 HTML
中定义的,而 “property
” 是在 DOM
上定义的。为了说明区别,假设咱们在 HTML
中有一个文本框:
<input type="text" value="Hello">。
const input = document.querySelector('input');
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello
可是在文本框中键入“ World!”后:
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello World!
复制代码
JavaScript
内置对象是很差的作法?扩展 JavaScript
内置(原生)对象意味着将属性或方法添加到其prototype
中。虽然听起来很不错,但事实上这样作很危险。想象一下,你的代码使用了一些库,它们经过添加相同的 contains
方法来扩展Array.prototype
,若是这两个方法的行为不相同,那么这些实现将会相互覆盖,你的代码将不能正常运行。
扩展内置对象的惟一使用场景是建立 polyfill
,本质上为老版本浏览器缺失的方法提供本身的实现,该方法是由 JavaScript
规范定义的。
document
中的load
事件和DOMContentLoaded
事件之间的区别是什么?当初始的 HTML
文档被彻底加载和解析完成以后,DOMContentLoaded
事件被触发,而无需等待样式表、图像和子框架的完成加载。
window
的load
事件仅在 DOM
和全部相关资源所有完成加载后才会触发。
==
和===
的区别是什么?==
是抽象相等运算符,而===
是严格相等运算符。==
运算符是在进行必要的类型转换后,再比较。===
运算符不会进行类型转换,因此若是两个值不是相同的类型,会直接返回false
。使用==
时,可能发生一些特别的事情,例如:
1 == '1'; // true
1 == [1]; // true
1 == true; // true
0 == ''; // true
0 == '0'; // true
0 == false; // true
个人建议是从不使用==运算符,除了方便与null或undefined比较时,a == null若是a为null或undefined将返回true。
var a = null;
console.log(a == null); // true
console.log(a == undefined); // true
复制代码
JavaScript
的同源策略。同源策略可防止 JavaScript
发起跨域请求。源被定义为 URI
、主机名
和端口号
的组合。此策略可防止页面上的恶意脚本经过该页面的文档对象模型,访问另外一个网页上的敏感数据。
duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) {
return arr.concat(arr);
}
duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
复制代码
三元
”这个词表明什么?“三元
”表示接受三个操做数:判断条件
,then表达式
和else
表达式。三元表达式不是 JavaScript
特有的,我不知道这个问题为何会出如今这里。
use strict
";?使用它有什么优缺点?'use strict
' 是用于对整个脚本或单个函数启用严格模式的语句。严格模式是可选择的一个限制 JavaScript
的变体一种方式 。
优势:
没法再意外建立全局变量。
会使引发静默失败(silently fail,即:不报错也没有任何效果)的赋值操抛出异常。
试图删除不可删除的属性时会抛出异常(以前这种操做不会产生任何效果)。
要求函数的参数名惟一。
全局做用域下,this的值为undefined。
捕获了一些常见的编码错误,并抛出异常。
禁用使人困惑或欠佳的功能。
复制代码
缺点:
缺失许多开发人员已经习惯的功能。
没法访问function.caller和function.arguments。
以不一样严格模式编写的脚本合并后可能致使问题。
总的来讲,我认为利大于弊,我历来不使用严格模式禁用的功能,所以我推荐使用严格模式。
复制代码
fizz
",5的倍数时输出 "buzz",同时为3和5的倍数时输出 "fizzbuzz
"。for (let i = 1; i <= 100; i++) {
let f = i % 3 == 0,
b = i % 5 == 0;
console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i);
}
复制代码
我不建议你在面试时写上面的代码。只要写得清晰便可。关于更多千奇百怪的 FizzBuzz 实现,请查看下面的参考连接。
每一个脚本均可以访问全局做用域,若是人人都使用全局命名空间来定义本身的变量,确定会发生冲突。使用模块模式(IIFE)将变量封装在本地命名空间中。
load
事件?这个事件有什么缺点吗?你知道一些代替方案吗,为何使用它们?在文档装载完成后会触发load
事件。此时,在文档中的全部对象都在 DOM
中,全部图像、脚本、连接和子框架都完成了加载。
DOM
事件DOMContentLoaded
将在页面的 DOM
构建完成后触发,但不要等待其余资源完成加载。若是在初始化以前不须要装入整个页面,这个事件是使用首选。
现现在,Web
开发人员将他们构建的产品称为 Web
应用,而不是网站。虽然这两个术语之间没有严格的区别,但网络应用每每具备高度的交互性和动态性,容许用户执行操做并接收他们的操做响应。在过去,浏览器从服务器接收 HTML
并渲染。当用户导航到其它 URL
时,须要整页刷新,服务器会为新页面发送新的 HTML
。这被称为服务器端渲染。
然而,在现代的 SPA
中,客户端渲染取而代之。浏览器从服务器加载初始页面、整个应用程序所需的脚本(框架、库、应用代码)和样式表。当用户导航到其余页面时,不会触发页面刷新。该页面的 URL
经过 HTML5 History API
进行更新。浏览器经过 AJAX
请求向服务器检索新页面所需的数据(一般采用 JSON
格式)。而后,SPA
经过 JavaScript
来动态更新页面,这些 JavaScript
在初始页面加载时已经下载。这种模式相似于原生移动应用的工做方式。
好处:
用户感知响应更快,用户切换页面时,再也不看到因页面刷新而致使的白屏。
对服务器进行的 HTTP 请求减小,由于对于每一个页面加载,没必要再次下载相同的资源。
客户端和服务器之间的关注点分离。能够为不一样平台(例如手机、聊天机器人、智能手表)创建新的客户端,而无需修改服务器代码。只要 API 没有修改,能够单独修改客户端和服务器上的代码。
复制代码
坏处:
因为加载了多个页面所需的框架、应用代码和资源,致使初始页面加载时间较长。
服务器还须要进行额外的工做,须要将全部请求路由配置到单个入口点,而后由客户端接管路由。
SPA 依赖于 JavaScript 来呈现内容,但并不是全部搜索引擎都在抓取过程当中执行 JavaScript,他们可能会在你的页面上看到空的内容。这无心中损害了应用的搜索引擎优化(SEO)。然而,当你构建应用时,大多数状况下,搜索引擎优化并非最重要的因素,由于并不是全部内容都须要经过搜索引擎进行索引。为了解决这个问题,能够在服务器端渲染你的应用,或者使用诸如 Prerender 的服务来“在浏览器中呈现你的 javascript,保存静态 HTML,并将其返回给爬虫”。
复制代码
Promises
及其 polyfill
的掌握程度如何?掌握它的工做原理。Promise
是一个可能在将来某个时间产生结果的对象:操做成功的结果或失败的缘由(例如发生网络错误)。 Promise
可能处于如下三种状态之一:fulfilled
、rejected
或 pending
。 用户能够对Promise
添加回调函数来处理操做成功的结果或失败的缘由。
一些常见的 polyfill
是$.deferred
、Q
和 Bluebird
,但不是全部的 polyfill
都符合规范。ES2015
支持Promises
,如今一般不须要使用 polyfills
。
优势:
避免可读性极差的回调地狱。
使用.then()编写的顺序异步代码,既简单又易读。
使用Promise.all()编写并行异步代码变得很容易。
缺点:
轻微地增长了代码的复杂度(这点存在争议)。
在不支持 ES2015 的旧版浏览器中,须要引入 polyfill 才能使用。
复制代码
React 和 Redux
React Devtools
Redux Devtools
Vue
Vue Devtools
JavaScript
Chrome Devtools
debugger声明
使用万金油console.log进行调试
复制代码
对象:
1.for循环:for (var property in obj) { console.log(property); }。可是,这还会遍历到它的继承属性,在使用以前,你须要加入obj.hasOwnProperty(property)检查。
2.Object.keys():Object.keys(obj).forEach(function (property) { ... })。Object.keys()方法会返回一个由一个给定对象的自身可枚举属性组成的数组。
3.Object.getOwnPropertyNames():Object.getOwnPropertyNames(obj).forEach(function (property) { ... })。Object.getOwnPropertyNames()方法返回一个由指定对象的全部自身属性的属性名(包括不可枚举属性但不包括 Symbol 值做为名称的属性)组成的数组。
数组:
4.for loops:for (var i = 0; i < arr.length; i++)。这里的常见错误是var是函数做用域而不是块级做用域,大多数时候你想要迭代变量在块级做用域中。ES2015 引入了具备块级做用域的let,建议使用它。因此就变成了:for (let i = 0; i < arr.length; i++)。
5.forEach:arr.forEach(function (el, index) { ... })。这个语句结构有时会更精简,由于若是你所须要的只是数组元素,你没必要使用index。还有every和some方法可让你提早终止遍历。
大多数状况下,我更喜欢.forEach方法,但这取决于你想要作什么。for循环有更强的灵活性,好比使用break提早终止循环,或者递增步数大于一。
复制代码
什么是 JavaScript
中的不可变对象的例子? 不变性有什么优势和缺点? 你如何在本身的代码中实现不变性? 可变对象 在建立以后是能够被改变的。
不可变对象 在建立以后是不能够被改变的。
在 JavaScript
中,string
和 number
从设计之初就是不可变(Immutable
)。 不可变 实际上是保持一个对象状态不变,这样作的好处是使得开发更加简单,可回溯,测试友好,减小了任何可能的反作用。可是,每当你想添加点东西到一个不可变(Immutable
)对象里时,它必定是先拷贝已存在的值到新实例里,而后再给新实例添加内容,最后返回新实例。相比可变对象,这势必会有更多内存、计算量消耗。 好比:构造一个纯函数
const student1 = {
school: 'Baidu',
name: 'HOU Ce',
birthdate: '1995-12-15',
};
const changeStudent = (student, newName, newBday) => {
return {
...student, // 使用解构
name: newName, // 覆盖name属性
birthdate: newBday, // 覆盖birthdate属性
};
};
const student2 = changeStudent(student1, 'YAN Haijing', '1990-11-10');
// both students will have the name properties
console.log(student1, student2);
// Object {school: "Baidu", name: "HOU Ce", birthdate: "1995-12-15"}
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"}
复制代码
同步函数阻塞,而异步函数不阻塞。在同步函数中,语句完成后,下一句才执行。在这种状况下,程序能够按照语句的顺序进行精确评估,若是其中一个语句须要很长时间,程序的执行会停滞很长时间。
异步函数一般接受回调做为参数,在调用异步函数后当即继续执行下一行。回调函数仅在异步操做完成且调用堆栈为空时调用。诸如从 Web
服务器加载数据或查询数据库等重负载操做应该异步完成,以便主线程能够继续执行其余操做,而不会出现一直阻塞,直到费时操做完成的状况(在浏览器中,界面会卡住)。
事件循环是一个单线程循环,用于监视调用堆栈并检查是否有工做即将在任务队列中完成。若是调用堆栈为空而且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行。
function foo() {}
和 var foo = function() {}
之间 foo
的用法上的区别。前者是函数声明,后者是函数表达式。关键的区别在于函数声明会使函数体提高(具备与变量相同的提高行为),但函数表达式的函数体不能。有关变量提高的更多解释,请参阅上面关于变量提高的问题。若是你试图在定义函数表达式以前调用它,你会获得一个Uncaught TypeError: XXX is not a function
的错误。
函数声明
foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}
函数表达式
foo(); // Uncaught TypeError: foo is not a function
var foo = function() {
console.log('FOOOOO');
};
复制代码
let
、var
和const
建立变量有什么区别?用var
声明的变量的做用域是它当前的执行上下文,它能够是嵌套的函数,也能够是声明在任何函数外的变量。let
和const
是块级做用域,意味着它们只能在最近的一组花括号(function
、if-else
代码块或 for
循环中)中访问。
function foo() {
// 全部变量在函数中均可访问
var bar = 'bar';
let baz = 'baz';
const qux = 'qux';
console.log(bar); // bar
console.log(baz); // baz
console.log(qux); // qux
}
console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
if (true) {
var bar = 'bar';
let baz = 'baz';
const qux = 'qux';
}
// 用 var 声明的变量在函数做用域上均可访问
console.log(bar); // bar
// let 和 const 定义的变量在它们被定义的语句块以外不可访问
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
var会使变量提高,这意味着变量能够在声明以前使用。let和const不会使变量提高,提早使用会报错。
console.log(foo); // undefined
var foo = 'foo';
console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization let baz = 'baz'; console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization
const bar = 'bar';
用var重复声明不会报错,但let和const会。
var foo = 'foo';
var foo = 'bar';
console.log(foo); // "bar"
let baz = 'baz';
let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared
let和const的区别在于:let容许屡次赋值,而const只容许一次。
// 这样不会报错。
let foo = 'foo';
foo = 'bar';
// 这样会报错。
const baz = 'baz';
baz = 'qux';
复制代码
(higher-order)
的定义是什么?高阶函数是将一个或多个函数做为参数的函数,它用于数据处理,也可能将函数做为返回结果。高阶函数是为了抽象一些重复执行的操做。一个典型的例子是map
,它将一个数组和一个函数做为参数。map使用这个函数来转换数组中的每一个元素,并返回一个包含转换后元素的新数组。JavaScript
中的其余常见示例是 forEach
、filter
和reduce
。高阶函数不只须要操做数组的时候会用到,还有许多函数返回新函数的用例。Function.prototype.bind
就是一个例子。
Map 示例:
假设咱们有一个由名字组成的数组,咱们须要将每一个字符转换为大写字母。
const names = ['irish', 'daisy', 'anna'];
不使用高阶函数的方法是这样:
const transformNamesToUppercase = function(names) {
const results = [];
for (let i = 0; i < names.length; i++) {
results.push(names[i].toUpperCase());
}
return results;
};
transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']
使用.map(transformerFn)使代码更简明
const transformNamesToUppercase = function(names) {
return names.map(name => name.toUpperCase());
};
transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']
复制代码
(destructuring)
对象或数组的例子。解构是 ES6
中新功能,它提供了一种简洁方便的方法来提取对象或数组的值,并将它们放入不一样的变量中。
数组解构
// 变量赋值
const foo = ['one', 'two', 'three'];
const [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// 变量交换
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
对象解构
// 变量赋值
const o = { p: 42, q: true };
const { p, q } = o;
console.log(p); // 42
console.log(q); // true
复制代码
ES6
的模板字符串为生成字符串提供了很大的灵活性,你能够举个例子吗?模板字面量 (Template literals)
是容许嵌入表达式的字符串字面量。你可使用多行字符串和字符串插值功能。
语法
`string text`
`string text line 1 string text line 2`
`string text ${expression} string text`;
tag`string text ${expression} string text`;
示例
console.log(`string text line 1 string text line 2`);
// "string text line 1 string text line 2"
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and\nnot 20."
//show函数采用rest参数的写法以下:
let name = '张三',
age = 20,
message = show`我来给你们介绍:${name}的年龄是${age}.`;
function show(stringArr, ...values) {
let output = '';
let index = 0;
for (; index < values.length; index++) {
output += stringArr[index] + values[index];
}
output += stringArr[index];
return output;
}
message; //"我来给你们介绍:张三的年龄是20."
复制代码
柯里化 (currying)
是一种模式,其中具备多个参数的函数被分解为多个函数,当被串联调用时,将一次一个地累积全部须要的参数。这种技术帮助编写函数式风格的代码,使代码更易读、紧凑。值得注意的是,对于须要被 curry
的函数,它须要从一个函数开始,而后分解成一系列函数,每一个函数都须要一个参数。
function curry(fn) {
if (fn.length === 0) {
return fn;
}
function _curried(depth, args) {
return function(newArgument) {
if (depth - 1 === 0) {
return fn(...args, newArgument);
}
return _curried(depth - 1, [...args, newArgument]);
};
}
return _curried(fn.length, []);
}
function add(a, b) {
return a + b;
}
var curriedAdd = curry(add);
var addFive = curriedAdd(5);
var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]
复制代码
在函数泛型编码时,ES6
的扩展运算符很是有用,由于咱们能够轻松建立数组和对象的拷贝,而无需使用 Object.create
、slice
或其余函数库。这个语言特性在 Redux
和 rx.js
的项目中常常用到。
function putDookieInAnyArray(arr) {
return [...arr, 'dookie'];
}
const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"]
const person = {
name: 'Todd',
age: 29,
};
const copyOfTodd = { ...person };
复制代码
ES6
的剩余参数语句提供了一个简写,容许咱们将不定数量的参数表示为一个数组。它就像是扩展运算符语法的反面,将数据收集到数组中,而不是解构数组。剩余参数语句在函数参数、数组和对象的解构赋值中有很大做用。
function addFiveToABunchOfNumbers(...numbers) {
return numbers.map(x => x + 5);
}
const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15]
const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4]
const { e, f, ...others } = {
e: 1,
f: 2,
g: 3,
h: 4,
}; // e: 1, f: 2, others: { g: 3, h: 4 }
复制代码
这取决于执行 JavaScript
的环境。
在客户端(浏览器环境)上,只要变量或函数在全局做用域 (window)
中声明,全部脚本均可以引用它们。或者,经过 RequireJS
采用异步模块定义 (AMD)
以得到更多模块化方法。
在服务器 (Node.js)
上,经常使用的方法是使用 CommonJS
。每一个文件都被视为一个模块,能够经过将它们附加到module.exports
对象来导出变量和函数。
ES2015
定义了一个模块语法,旨在替换 AMD
和 CommonJS
。 这最终将在浏览器和 Node
环境中获得支持。
静态类成员(属性或方法)不绑定到某个类的特定实例,无论哪一个实例引用它,都具备相同的值。静态属性一般是配置变量,而静态方法一般是纯粹的实用函数,不依赖于实例的状态。
文中若有错误,欢迎在评论区指正,若是这篇文章帮助到了你,欢迎点赞👍和关注,😀。