者|FullStack.Cafe
出处|前端之巅
根据 Stack Overflow 的 2018 年度调查,JavaScript 连续六年成为最常用的编程语言。所以我们必须面对这样的现实,JavaScript 已经成为全栈开发技能的基石,在全栈开发面试中都会不可避免地涉及到与 JavaScript 有关的问题。FullStack.Cafe 汇编了最常见的 JavaScript 面试问题和答案,希望能够帮助读者找到下一份梦想中的工作。
Q1:JavaScript 中的强制转型(coercion)是指什么?
难度:0
在 JavaScript 中,两种不同的内置类型间的转换被称为强制转型。强制转型在 JavaScript 中有两种形式:显式和隐式。
这是一个显式强制转型的例子:
var a="42"; var b=Number( a ); a; // "42" b; // 42 -- 是个数字!
这是一个隐式强制转型的例子:
var a="42"; var b=a * 1; // "42" 隐式转型成 42 a; // "42" b; // 42 -- 是个数字!
Q2:JavaScript 中的作用域(scope)是指什么?
难度:?
在 JavaScript 中,每个函数都有自己的作用域。作用域基本上是变量以及如何通过名称访问这些变量的规则的集合。只有函数中的代码才能访问函数作用域内的变量。
同一个作用域中的变量名必须是唯一的。一个作用域可以嵌套在另一个作用域内。如果一个作用域嵌套在另一个作用域内,最内部作用域内的代码可以访问另一个作用域的变量。
Q3:解释 JavaScript 中的相等性。
难度:?
JavaScript 中有严格比较和类型转换比较:
var a="42"; var b=42; a==b; // true a===b; // false
一些简单的规则:
Q4:解释什么是回调函数,并提供一个简单的例子。
难度:??
回调函数是可以作为参数传递给另一个函数的函数,并在某些操作完成后执行。下面是一个简单的回调函数示例,这个函数在某些操作完成后打印消息到控制台。
function modifyArray(arr, callback) { // 对 arr 做一些操作 arr.push(100); // 执行传进来的 callback 函数 callback(); } var arr=[1, 2, 3, 4, 5]; modifyArray(arr, function() { console.log("array has been modified", arr); });
Q5:“use strict”的作用是什么?
难度:??
use strict 出现在 JavaScript 代码的顶部或函数的顶部,可以帮助你写出更安全的 JavaScript 代码。如果你错误地创建了全局变量,它会通过抛出错误的方式来警告你。例如,以下程序将抛出错误:
function doSomething(val) { "use strict"; x=val + 10; }
它会抛出一个错误,因为 x 没有被定义,并使用了全局作用域中的某个值对其进行赋值,而 use strict 不允许这样做。下面的小改动修复了这个错误:
function doSomething(val) { "use strict"; var x=val + 10; }
Q6:解释 JavaScript 中的 null 和 undefined。
难度:??
JavaScript 中有两种底层类型:null 和 undefined。它们代表了不同的含义:
Q7:编写一个可以执行如下操作的函数。
难度:??
var addSix=createBase(6); addSix(10); // 返回 16 addSix(21); // 返回 27
可以创建一个闭包来存放传递给函数 createBase 的值。被返回的内部函数是在外部函数中创建的,内部函数就成了一个闭包,它可以访问外部函数中的变量,在本例中是变量 baseNumber。
function createBase(baseNumber) { return function(N) { // 我们在这里访问 baseNumber,即使它是在这个函数之外声明的。 // JavaScript 中的闭包允许我们这么做。 return baseNumber + N; } } var addSix=createBase(6); addSix(10); addSix(21);
Q8:解释 JavaScript 中的值和类型。
难度:??
JavaScript 有类型值,但没有类型变量。JavaScript 提供了以下几种内置类型:
Q9:解释事件冒泡以及如何阻止它?
难度:??
事件冒泡是指嵌套最深的元素触发一个事件,然后这个事件顺着嵌套顺序在父元素上触发。
防止事件冒泡的一种方法是使用 event.cancelBubble 或 event.stopPropagation()(低于 IE 9)。
Q10:JavaScript 中的 let 关键字有什么用?
难度:??
除了可以在函数级别声明变量之外,ES6 还允许你使用 let 关键字在代码块({..})中声明变量。
Q11:如何检查一个数字是否为整数?
难度:??
检查一个数字是小数还是整数,可以使用一种非常简单的方法,就是将它对 1 进行取模,看看是否有余数。
function isInt(num) { return num % 1===0; } console.log(isInt(4)); // true console.log(isInt(12.2)); // false console.log(isInt(0.3)); // false
Q12:什么是 IIFE(立即调用函数表达式)?
难度:???
它是立即调用函数表达式(Immediately-Invoked Function Expression),简称 IIFE。函数被创建后立即被执行:
(function IIFE(){ console.log( "Hello!" ); })(); // "Hello!"
在避免污染全局命名空间时经常使用这种模式,因为 IIFE(与任何其他正常函数一样)内部的所有变量在其作用域之外都是不可见的。
Q13:如何在 JavaScript 中比较两个对象?
难度:???
对于两个非原始值,比如两个对象(包括函数和数组),==和===比较都只是检查它们的引用是否匹配,并不会检查实际引用的内容。
例如,默认情况下,数组将被强制转型成字符串,并使用逗号将数组的所有元素连接起来。所以,两个具有相同内容的数组进行==比较时不会相等:
var a=[1,2,3]; var b=[1,2,3]; var c="1,2,3"; a==c; // true b==c; // true a==b; // false
对于对象的深度比较,可以使用 deep-equal 这个库,或者自己实现递归比较算法。
Q14:你能解释一下 ES5 和 ES6 之间的区别吗?
难度:???
以下是 ES5 和 ES6 之间的一些主要区别:
const greetings=(name)=> { return `hello ${name}`; } const greetings=name=> `hello ${name}`;
常量在很多方面与其他语言中的常量一样,但有一些需要注意的地方。常量表示对值的“固定引用”。因此,在使用常量时,你实际上可以改变变量所引用的对象的属性,但无法改变引用本身。
const NAMES=[]; NAMES.push("Jim"); console.log(NAMES.length===1); // true NAMES=["Steve", "John"]; // error
新的 ES6 关键字 let 允许开发人员声明块级别作用域的变量。let 不像 var 那样可以进行提升。
默认参数允许我们使用默认值初始化函数。如果省略或未定义参数,则使用默认值,也就是说 null 是有效值。
// 基本语法 function multiply (a, b=2) { return a * b; } multiply(5); // 10
ES6 引入了对类(关键字 class)、构造函数(关键字 constructor)和用于继承的 extend 关键字的支持。
for…of 语句将创建一个遍历可迭代对象的循环。
const obj1={ a: 1, b: 2 } const obj2={ a: 2, c: 3, d: 4} const obj3={...obj1, ...obj2}
promise 提供了一种机制来处理异步操作结果。你可以使用回调来达到同样的目的,但是 promise 通过方法链接和简洁的错误处理带来了更高的可读性。
const isGreater=(a, b)=> { return new Promise ((resolve, reject)=> { if(a > b) { resolve(true) } else { reject(false) } }) } isGreater(1, 2) .then(result=> { console.log('greater') }) .catch(result=> { console.log('smaller') })
const myModule={ x: 1, y: ()=> { console.log('This is ES5') }} export default myModule; import myModule from './myModule';
问题 15:解释 JavaScript 中“undefined”和“not defined”之间的区别。
难度:???
在 JavaScript 中,如果你试图使用一个不存在且尚未声明的变量,JavaScript 将抛出错误“var name is not defined”,让后脚本将停止运行。但如果你使用 typeof undeclared_variable,它将返回 undefined。
在进一步讨论之前,先让我们理解声明和定义之间的区别。
“var x”表示一个声明,因为你没有定义它的值是什么,你只是声明它的存在。
var x; // 声明 x console.log(x); // 输出: undefined
“var x=1”既是声明又是定义(我们也可以说它是初始化),x 变量的声明和赋值相继发生。在 JavaScript 中,每个变量声明和函数声明都被带到了当前作用域的顶部,然后进行赋值,这个过程被称为提升(hoisting)。
当我们试图访问一个被声明但未被定义的变量时,会出现 undefined 错误。
var x; // 声明 if(typeof x==='undefined') // 将返回 true
当我们试图引用一个既未声明也未定义的变量时,将会出现 not defined 错误。
console.log(y); // 输出: ReferenceError: y is not defined
Q16:匿名和命名函数有什么区别?
难度:???
var foo=function() { // 赋给变量 foo 的匿名函数 // .. }; var x=function bar(){ // 赋给变量 x 的命名函数 bar // .. }; foo(); // 实际执行函数 x();
Q17:Javascript 中的“闭包”是什么?举个例子?
难度:????
闭包是在另一个函数(称为父函数)中定义的函数,并且可以访问在父函数作用域中声明和定义的变量。
闭包可以访问三个作用域中的变量:
var globalVar="abc"; // 自调用函数 (function outerFunction (outerArg) { // outerFunction 作用域开始 // 在 outerFunction 函数作用域中声明的变量 var outerFuncVar='x'; // 闭包自调用函数 (function innerFunction (innerArg) { // innerFunction 作用域开始 // 在 innerFunction 函数作用域中声明的变量 var innerFuncVar="y"; console.log( "outerArg=" + outerArg + "\n" + "outerFuncVar=" + outerFuncVar + "\n" + "innerArg=" + innerArg + "\n" + "innerFuncVar=" + innerFuncVar + "\n" + "globalVar=" + globalVar); // innerFunction 作用域结束 })(5); // 将 5 作为参数 // outerFunction 作用域结束 })(7); // 将 7 作为参数
innerFunction 是在 outerFunction 中定义的闭包,可以访问在 outerFunction 作用域内声明和定义的所有变量。除此之外,闭包还可以访问在全局命名空间中声明的变量。
上述代码的输出将是:
outerArg=7 outerFuncVar=x innerArg=5 innerFuncVar=y globalVar=abc
Q18:如何在 JavaScript 中创建私有变量?
难度:????
要在 JavaScript 中创建无法被修改的私有变量,你需要将其创建为函数中的局部变量。即使这个函数被调用,也无法在函数之外访问这个变量。例如:
function func() { var priv="secret code"; } console.log(priv); // throws error
要访问这个变量,需要创建一个返回私有变量的辅助函数。
function func() { var priv="secret code"; return function() { return priv; } } var getPriv=func(); console.log(getPriv()); //=> secret code
Q19:请解释原型设计模式。
难度:????
原型模式可用于创建新对象,但它创建的不是非初始化的对象,而是使用原型对象(或样本对象)的值进行初始化的对象。原型模式也称为属性模式。
原型模式在初始化业务对象时非常有用,业务对象的值与数据库中的默认值相匹配。原型对象中的默认值被复制到新创建的业务对象中。
经典的编程语言很少使用原型模式,但作为原型语言的 JavaScript 在构造新对象及其原型时使用了这个模式。
Q20:判断一个给定的字符串是否是同构的。
难度:????
如果两个字符串是同构的,那么字符串 A 中所有出现的字符都可以用另一个字符替换,以便获得字符串 B,而且必须保留字符的顺序。字符串 A 中的每个字符必须与字符串 B 的每个字符一对一对应。
isIsomorphic("egg", 'add'); // true isIsomorphic("paper", 'title'); // true isIsomorphic("kick", 'side'); // false function isIsomorphic(firstString, secondString) { // 检查长度是否相等,如果不相等, 它们不可能是同构的 if (firstString.length !==secondString.length) return false var letterMap={}; for (var i=0; i < firstString.length; i++) { var letterA=firstString[i], letterB=secondString[i]; // 如果 letterA 不存在, 创建一个 map,并将 letterB 赋值给它 if (letterMap[letterA]===undefined) { letterMap[letterA]=letterB; } else if (letterMap[letterA] !==letterB) { // 如果 letterA 在 map 中已存在, 但不是与 letterB 对应, // 那么这意味着 letterA 与多个字符相对应。 return false; } } // 迭代完毕,如果满足条件,那么返回 true。 // 它们是同构的。 return true; }
Q21:“Transpiling”是什么意思?
难度:????
对于语言中新加入的语法,无法进行 polyfill。因此,更好的办法是使用一种工具,可以将较新代码转换为较旧的等效代码。这个过程通常称为转换(transpiling),就是 transforming + compiling 的意思。
通常,你会将转换器(transpiler)加入到构建过程中,类似于 linter 或 minifier。现在有很多很棒的转换器可选择:
Q22:“this”关键字的原理是什么?请提供一些代码示例。
难度:????
在 JavaScript 中,this 是指正在执行的函数的“所有者”,或者更确切地说,指将当前函数作为方法的对象。
function foo() { console.log( this.bar ); } var bar="global"; var obj1={ bar: "obj1", foo: foo }; var obj2={ bar: "obj2" }; foo(); // "global" obj1.foo(); // "obj1" foo.call( obj2 ); // "obj2" new foo(); // undefined
Q23:如何向 Array 对象添加自定义方法,让下面的代码可以运行?
难度:????
var arr=[1, 2, 3, 4, 5]; var avg=arr.average(); console.log(avg);
JavaScript 不是基于类的,但它是基于原型的语言。这意味着每个对象都链接到另一个对象(也就是对象的原型),并继承原型对象的方法。你可以跟踪每个对象的原型链,直到到达没有原型的 null 对象。我们需要通过修改 Array 原型来向全局 Array 对象添加方法。
Array.prototype.average=function() { // 计算 sum 的值 var sum=this.reduce(function(prev, cur) { return prev + cur; }); // 将 sum 除以元素个数并返回 return sum / this.length; } var arr=[1, 2, 3, 4, 5]; var avg=arr.average(); console.log(avg); //=> 3
Q24:什么是 JavaScript 中的提升操作?
难度:????
提升(hoisting)是 JavaScript 解释器将所有变量和函数声明移动到当前作用域顶部的操作。有两种类型的提升:
无论 var(或函数声明)出现在作用域的什么地方,它都属于整个作用域,并且可以在该作用域内的任何地方访问它。
var a=2; foo(); // 因为`foo()`声明被"提升",所以可调用 function foo() { a=3; console.log( a ); // 3 var a; // 声明被"提升"到 foo() 的顶部 } console.log( a ); // 2
Q25:以下代码输出的结果是什么?
难度:????
0.1 + 0.2===0.3
这段代码的输出是 false,这是由浮点数内部表示导致的。0.1 + 0.2 并不刚好等于 0.3,实际结果是 0.30000000000000004。解决这个问题的一个办法是在对小数进行算术运算时对结果进行舍入。
Q26:请描述一下 Revealing Module Pattern 设计模式。
难度:?????
暴露模块模式(Revealing Module Pattern)是模块模式的一个变体,目的是维护封装性并暴露在对象中返回的某些变量和方法。如下所示:
var Exposer=(function() { var privateVariable=10; var privateMethod=function() { console.log('Inside a private method!'); privateVariable++; } var methodToExpose=function() { console.log('This is a method I want to expose!'); } var otherMethodIWantToExpose=function() { privateMethod(); } return { first: methodToExpose, second: otherMethodIWantToExpose }; })(); Exposer.first(); // 输出: This is a method I want to expose! Exposer.second(); // 输出: Inside a private method! Exposer.methodToExpose; // undefined
它的一个明显的缺点是无法引用私有方法。
英文原文
https://www.fullstack.cafe/blog/top-26-javascript-interview-questions-and-answers-in-2019
、定义变量
ECMAScript的变量是松散型类型的,所谓松散型类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符而已。定义变量时要使用var操作符(var是一个关键字),后跟变量名(即一个标识符),如下所示:
var message;
这行代码定义了一个名为message的变量,该变量可以用来保存任何值(像这样未经过初始化的变量,会保存一个特殊的值—undefined)。ECMAScript也支持直接初始化变量,因此在定义变量的同时就可以设置变量的值,如下所示:
var message="hi";
在此,变量message保存了一个字符串"hi"。像这样初始化变量并不会把它标记为字符串类型;初始化的过程就是给变量赋一个值那么简单。因此,可以在修改变量值的同时修改值的类型,如下所示:
var message="hi"; message=100; //有效,但不推荐
在这个例子中,变量message一开始保存了一个字符串值"hi",然后该值又被一个数字值100取代。虽然我们不建议修改变量所保存的类型,但这种操作在ECMAScript中完全有效。
可以使用一条语句定义多个变量,只要像下面这样把每个变量(初始化或不初始化均可)用逗号分隔开即可:
var message="hi", found=false, age=29;
这个例子定义并初始化了3个变量。同样由于ECMAScript是松散类型的,因而使用不同类型初始化变量的操作可以放在一条语句中来完成。虽然代码里的换行和变量缩进不是必需的,但这样做可以提高可读性。
在严格模式下,不能定义名为eval和arguments的变量,否则会导致语法错误。
二、定义局部变量和全局变量
有一点必须注意,即用var操作符定义的变量将成为定义该变量的作用域中的局部变量。也就是说,如果在函数中使用var定义一个变量,那么这个变量在函数退出后就会被销毁,例如:
function test(){ var message="hi"; //局部变量 } test(); alert(message); //错误!
这里,变量message是在函数中使用var定义的。当函数被调用时,就会创建该变量并为其赋值。而在此之后,这个变量又会立即被销毁,因此例子中的下一行代码就会导致错误。不过可以像下面这样省略var操作符,从而创建一个局部变量:
function test(){ message="hi"; //全局变量 } test(); alert(message); //"hi"
这个例子省略了var操作符,因而message就成了全局变量。这样,只要调用过一次test()函数,这个变量就有了定义,就可以在函数外部的任何地方被访问到。
虽然省略var操作符可以定义全局变量,但这也不是我们推荐的做法。因为在局部作用域中定义的全局变量很难维护,而且如果有意地忽略了var操作符,也会由于相应变量不会马上就有定义而导致不必要的混乱。给未经声明的变量赋值在严格模式下会导致抛出ReferenceError错误。
avaScript中变量分为命名,声明,类型,作用域四个方面。
变量不只是你想的那样简单粗暴,其实还有更好的方式方法,要学会更简单更高效的去使用它,让你的开发简洁明了,看了下面这篇文章后,你讲改掉你的陋习,从基础做起,做最好的程序猿。
JavaScript 变量是存储数据值的容器。
关于JavaScript变量我们将会从下面几个方面出发:
命名主要分为命名的方法和规则。
1、命名的方法
关于命名的方法主要分为两种匈牙利命名法和驼峰命名法。
匈牙利命名法:
变量名=类型+对象描述
这就是匈牙利命名法的规则了,它的变量名是根据你想定义的变量的类型 + 你想定义变量的描述来进行变量的命名。在我的JavaScript编程中我用的还是比较多的,这样可以高效的区分多个变量名的意义了,看看下面这张图就明白了:
匈牙利命名法
我们利用变量类型的首字母+对象描述来完成。
驼峰命名法
这就很常见了,作为程序员基本上都已这种方法来进行命名,驼峰命名法也可分为全部小写和大小写混合,全部小写是将单词与单词间用下划线分割,大小写混合又分为大驼峰和小驼峰,大驼峰是将每个单词的首字母大写,小驼峰是将第一个单词的首字母小写,其他首字母大写。同样的来看看这张图:
驼峰命名法
小结:你定义的变量,我们要直接从变量名中得到信息,就像看见你我就知道你叫什么名,长得帅不帅,要有它存在的意义,你有你爸妈做主,那名字当然响亮有意义,你的代码只能你做主,它也是代表你的脸面,好不好,有没有意义你说了算。别再用a,b,c,d啦。
2、命名的规则
所有JavaScript变量必须以唯一的名称的标识存在,这些唯一的名称被称为标识符,你的名字可以存在两个以上,但JS就不行,看看它构造变量名称(唯一标识符)的通用规则是什么?
遵守上面的这些规则再加上好的命名方法就可以是一个好的变量名,这里的关键词和保留字是不能作为JavaScript变量名的,例如:
关键词不能用作变量名
保留字:以后可能做为关键字的存在
在ES5的时候,JavaScript 使用 var 和 function 来声明变量, 在ES6 中又添加了let、const、import、 Class 这几种声明变量的方式。
长期以来我们经常用 var 来声明变量,我们不难看出使用 var 声明变量有几个特点:
注意在声明时候的陋习:
这些都是在声明变量时的陋习,尽可能不要犯这些错误。
我们来看看下面这道题:
var getName;
getName=function(){
alert(1)
};
function getName(){
alert(2)
};
function getName(){
alert(2)
};
var getName=function()alert(1)};
getName();// 1
这就是JavaScript变量的提升,不仅只有var 声明才会提前,以function fn(){}这种形式声明的函数,会被提升到作用域的最最顶部,然后再是变量的提升。
此外再教你一个javascript如何用变量值做变量名声明?用eval就可以这样声明。
var ar=["a","b","c","d"];
for(var i=0;i<ar.length;i++){
eval('var '+ar[i]+'=0;');
}
alert(a);
alert(b);
JavaScript中的变量类型分为值类型和引用类型,而值类型又叫做数据类型。
值类型有:
值类型的特点是:
引用类型有:
引用类型的特点是:
关于数据类型这方面我会在以后的文章详细说明使用方法。
JavaScript的作用域的内容分为全局变量,局部变量,优先级,作用域链这四个知识点。作用域决定了这些变量的可访问性,在JavaScript函数中声明的变量,会成为函数的局部变量,局部变量的作用域是局部的,只能在函数内访问他们,在JavaScript中,作用域是很关键的知识点。
1、全局变量
全局变量是包含在函数体外的变量,在函数体内定义的无var的变量,是可以在任何位置进行调用的。全局变量的作用域是全局的,网页的所有脚本和函数都能够访问它,例如:
var carName=" BMW";
// 此处的代码能够使用 carName 变量
function myFunction() {
// 此处的代码也能够使用 carName 变量
}
2、局部变量
局部变量是函数中声明的变量,会成为函数的局部变量,不能作为函数外的变量使用,局部变量的作用域是局部的,只能在函数内访问,例如:
// 此处的代码不能使用 carName 变量
function myFunction() {
var carName="porsche";
// 此处的代码能使用 carName 变量
}
额外注意的是:
自动全局:如果你没有为未声明的变量赋值,此变量会成为全局变量,就是在函数外面声明了一个为赋值的变量,在函数体内进行赋值,那它也是一个全局变量,例如:
myFunction();
// 此处的代码能够使用 carName 变量
function myFunction() {
carName="BMW";
}
严格模式:所有的浏览器都支持“严格模式”运行JavaScript,在严格模式中不会自动创建全局变量。
JavaScript变量的有效期:JavaScript变量的有效期始于被创建时,局部变量会在函数完成时被删除,全局变量会在你关闭页面时被删除。
函数参数也是函数内的局部变量。
3、优先级
这里面你就明白访问的优先级就行了,
局部变量高于同名全局变量
参数变量高于同名全局变量
局部变量高于同名参数变量
4、作用域链
这么说吧,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置,这就是JavaScript的作用域链。
先来看看这段代码:
var a=1;
function fn1 () {
var a=2;
function fn2 () {
var a=3;
console.log(a);
}
fn2 ();
}
fn1(); // 3
在这段代码JavaScript在查找a变量标识符的值的时候,会从函数体fn2内部向上查找变量声明,这时候就会发现内部已经有了a变量,它就不会继续查找了,如果没有,它会向上查找,这时候已经找到了,它就会打印3。
在脚本开头声明所有的变量是个好习惯,在日常程序中,被声明的变量经常是不带值得,基本上是当做被计算的内容,或是之后被提供的数据,比如数据的输入,不带有值的变量,它的值将是undefined。
知道了对象的描述,我们就要用有意义的变量名去声明,然后使用不同的作用域去访问它,使用它,基础的心里清楚了,这对你写代码是个很好地帮助。
本文为‘Web前端进阶指南’原创,转载请说明出处,手动码字不易,喜欢的小伙伴们别忘了顺手点个赞加个关注哈,有什么不懂的下方留言评论或私信。谢谢大家哈!
*请认真填写需求信息,我们会在24小时内与您取得联系。