You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function createGraph() {
// createGraph logic here
}
function drawGraph() {
// drawGraph logic here
}
</script>
当我们使用这个解决方案时,我们覆盖了库提供给我们的那两个方法。
另一种方式是我们自己改名称:
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
function myCreateGraph() {
// createGraph logic here
}
function myDrawGraph() {
// drawGraph logic here
}
</script>
当我们使用这个解决方案时,我们把那些函数调用更改为新的函数名。
还有一种方法就是使用IIFE:
<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
const graphUtility = (function () {
function createGraph() {
// createGraph logic here
}
function drawGraph() {
// drawGraph logic here
}
return {
createGraph,
drawGraph
}
})
</script>
var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
li[i].addEventListener('click', function (e) {
console.log(i);
})
假设我们有一个带有list-group类的ul元素,它有5个li子元素。 当我们单击单个li元素时,打印对应的下标值。但在此外上述代码不起作用,这里每次点击 li 打印 i 的值都是5,这是由于闭包的原因。
var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
(function (currentIndex) {
li[currentIndex].addEventListener('click', function (e) {
console.log(currentIndex);
})
})(i);
}
function one() {
return Array.prototype.slice.call(arguments);
}
注意:箭头函数中没有arguments对象。
function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}
const four = () => arguments;
four(); // Throws an error - arguments is not defined
当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。
const o1 = {};
console.log(o1.toString()); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
var声明变量存在变量提升,let和const不存在变量提升:
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
let和const声明形成块作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
同一作用域下let和const不能声明同名变量,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。
暂存死区
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
//ES5 Version
Employee.prototype = Object.create(Person.prototype);
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
Employee.prototype.toString = function () {
return "[object Employee]";
}
//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}
所以我们要怎么知道它在内部使用原型?
class Something {
}
function AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true
// 回调地狱
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})
由于篇幅过长,我将此系列分成上中下三篇,上篇:
看完这几道 JavaScript 面试题,让你与考官对答如流(上)
Array.prototype.map
方法Array.prototype.filter
方法Array.prototype.reduce
方法b
会变成一个全局变量?var
,let
和const
的区别是什么Set
对象,它是如何工作的?26. 什么是 IIFE,它的用途是什么?
IIFE或立即调用的函数表达式是在创建或声明后将被调用或执行的函数。 创建IIFE的语法是,将
function (){}
包裹在在括号()
内,然后再用另一个括号()
调用它,如:(function(){})()
这些示例都是有效的IIFE。 倒数第二个救命表明我们可以将参数传递给IIFE函数。 最后一个示例表明,我们可以将
IIFE
的结果保存到变量中,以便稍后使用。IIFE的一个主要作用是避免与全局作用域内的其他变量命名冲突或污染全局命名空间,来个例子。
假设我们引入了一个
omelibr.js
的链接,它提供了一些我们在代码中使用的全局函数,但是这个库有两个方法我们没有使用:createGraph
和drawGraph
,因为这些方法都有bug
。我们想实现自己的createGraph
和drawGraph
方法。解决此问题的一种方法是直接覆盖:
当我们使用这个解决方案时,我们覆盖了库提供给我们的那两个方法。
另一种方式是我们自己改名称:
当我们使用这个解决方案时,我们把那些函数调用更改为新的函数名。
还有一种方法就是使用IIFE:
在此解决方案中,我们要声明了
graphUtility
变量,用来保存IIFE执行的结果,该函数返回一个包含两个方法createGraph
和drawGraph
的对象。IIFE 还可以用来解决一个常见的面试题:
假设我们有一个带有
list-group
类的ul
元素,它有5
个li
子元素。 当我们单击单个li
元素时,打印对应的下标值。但在此外上述代码不起作用,这里每次点击li
打印i
的值都是5
,这是由于闭包的原因。闭包只是函数记住其当前作用域,父函数作用域和全局作用域的变量引用的能力。 当我们在全局作用域内使用
var
关键字声明变量时,就创建全局变量i
。 因此,当我们单击li
元素时,它将打印5
,因为这是稍后在回调函数中引用它时i
的值。使用 IIFE 可以解决此问题:
该解决方案之所以行的通,是因为IIFE会为每次迭代创建一个新的作用域,我们捕获
i
的值并将其传递给currentIndex
参数,因此调用IIFE时,每次迭代的currentIndex
值都是不同的。27. Function.prototype.apply 方法的用途是什么?
apply()
方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。28.
Function.prototype.call
方法的用途是什么?call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。注意:该方法的语法和作用与
apply()
方法类似,只有一个区别,就是call()
方法接受的是一个参数列表,而apply()
方法接受的是一个包含多个参数的数组。29. Function.prototype.apply 和 Function.prototype.call 之间有什么区别?
apply()
方法可以在使用一个指定的this
值和一个参数数组(或类数组对象)的前提下调用某个函数或方法。call()
方法类似于apply()
,不同之处仅仅是call()
接受的参数是参数列表。30. Function.prototype.bind 的用途是什么?
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。31. 什么是函数式编程? JavaScript 的哪些特性使其成为函数式语言的候选语言?
函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。
函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范示的其他示例也包括面向对象编程和过程编程。
函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,并且 相关文献对新人来说是不好理解的。
JavaScript支持闭包和高阶函数是函数式编程语言的特点。
32. 什么是高阶函数?
高阶函数只是将函数作为参数或返回值的函数。
33. 为什么函数被称为一等公民?
在JavaScript中,函数不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值
(var func = function(){})
、传参(function func(x,callback){callback();})
、返回(function(){return function(){}})
,这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function
类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。34. 手动实现
Array.prototype.map 方法
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。35. 手动实现
Array.prototype.filter
方法filter()
方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。36. 手动实现
Array.prototype.reduce
方法reduce()
方法对数组中的每个元素执行一个由您提供的reducer
函数(升序执行),将其结果汇总为单个返回值。37. arguments 的对象是什么?
arguments
对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]
来访问单个值,但它没有数组中的内置方法,如:forEach
、reduce
、filter
和map
。我们可以使用
Array.prototype.slice
将arguments
对象转换成一个数组。注意:箭头函数中没有
arguments
对象。当我们调用函数
four
时,它会抛出一个ReferenceError: arguments is not defined error
。使用rest
语法,可以解决这个问题。这会自动将所有参数值放入数组中。
38. 如何创建一个没有 prototype(原型)的对象?
我们可以使用
Object.create
方法创建没有原型的对象。39. 为什么在调用这个函数时,代码中的
b
会变成一个全局变量?原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:
首先,表达式
b = 0
求值,在本例中b
没有声明。因此,JS引擎在这个函数外创建了一个全局变量b
,之后表达式b = 0
的返回值为0
,并赋给新的局部变量a
。我们可以通过在赋值之前先声明变量来解决这个问题。
40. ECMAScript 是什么?
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件
只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得做什么!
JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!
——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己,
那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。
41. ES6或ECMAScript 2015有哪些新特性?
42.
var
,let
和const
的区别是什么?var
声明的变量会挂载在window
上,而let
和const
声明的变量不会:var
声明变量存在变量提升,let
和const
不存在变量提升:let
和const
声明形成块作用域同一作用域下
let
和const
不能声明同名变量,而var
可以暂存死区
const
43. 什么是箭头函数?
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this
,arguments
,super
或new.target
。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。在本例中,ES5 版本中有
function(){}
声明和return
关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()
括号,不需要return
语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号。
箭头函数不能访问
arguments
对象。所以调用第一个getArgs
函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。箭头函数没有自己的
this
值。 它捕获词法作用域函数的this
值,在此示例中,addAll
函数将复制computeResult
方法中的this
值,如果我们在全局作用域声明箭头函数,则this
值为window
对象。44. 什么是类?
类(class)
是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。重写方法并从另一个类继承。
所以我们要怎么知道它在内部使用原型?
45. 什么是模板字符串?
模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。
在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦:
在ES5版本中,我们需要添加
\n
以在字符串中添加新行。 在模板字符串中,我们不需要这样做。在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用
+
运算符。 在模板字符串s中,我们可以使用${expr}
嵌入一个表达式,这使其比 ES5 版本更整洁。46. 什么是对象解构?
对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象:
从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。
使用解构方式语法就变得简洁多了:
我们还可以为属性取别名:
当然如果属性值为
undefined
时,我们还可以指定默认值:47. 什么是 ES6 模块?
模块使我们能够将代码基础分割成多个文件,以获得更高的可维护性,并且避免将所有代码放在一个大文件中。在 ES6 支持模块之前,有两个流行的模块。
基本上,使用模块的方式很简单,
import
用于从另一个文件中获取功能或几个功能或值,同时export
用于从文件中公开功能或几个功能或值。导出
使用 ES5 (CommonJS)
使用 ES6 模块
在另一个文件中导入函数
在文件中导出单个功能或默认导出
使用 ES5 (CommonJS)
使用ES6 Modules
从另一个文件导入单个功能
使用ES5 (CommonJS)
使用 ES6 Modules
48. 什么是
Set
对象,它是如何工作的?Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
我们可以使用
Set
构造函数创建Set
实例。我们可以使用
add
方法向Set
实例中添加一个新值,因为add
方法返回Set
对象,所以我们可以以链式的方式再次使用add
。如果一个值已经存在于Set
对象中,那么它将不再被添加。我们可以使用
has
方法检查Set
实例中是否存在特定的值。我们可以使用
size
属性获得Set
实例的长度。可以使用
clear
方法删除Set
中的数据。我们可以使用
Set
对象来删除数组中重复的元素。49. 什么是回调函数?
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。
在JavaScript中函数也是对象的一种,同样对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。
在本例中,我们等待
id
为btnAdd
的元素中的click
事件,如果它被单击,则执行clickCallback
函数。回调函数向某些数据或事件添加一些功能。数组中的
reduce
、filter
和map
方法需要一个回调作为参数。回调的一个很好的类比是,当你打电话给某人,如果他们不接,你留下一条消息,你期待他们回调。调用某人或留下消息的行为是事件或数据,回调是你希望稍后发生的操作。50. Promise 是什么?
Promise 是异步编程的一种解决方案:从语法上讲,
promise
是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise
有三种状态:pending(等待态)
,fulfiled(成功态)
,rejected(失败态)
;状态一旦改变,就不会再变。创造promise
实例后,它会立即执行。如果我们在回调内部有另一个异步操作,则此方法存在问题。 我们将有一个混乱且不可读的代码。 此代码称为**“回调地狱”**。
如果我们在这段代码中使用
promise
,它将更易于阅读、理解和维护。promise
有三种不同的状态:pending
状态的Promise
对象会触发fulfilled/rejected
状态,在其状态处理方法中可以传入参数/失败信息。当操作成功完成时,Promise 对象的then
方法就会被调用;否则就会触发catch
。如:由于篇幅过长,我将此系列分成上中下三篇,下篇我们在见。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:
https://dev.to/macmacky/70-javascript-interview-questions-5gfi#1-whats-the-difference-between-undefined-and-null
交流
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered: