为了账号安全,请及时绑定邮箱和手机立即绑定

前端面试整理—Javascript(二)

图片描述

本文来自于我的微信公众号 — 闪兔网络工作室前端面试整理—Javascipt问题,转载请保留链接 ;)

这篇文章紧接前端面试整理—Javascript(一),欢迎继续阅读。

=====的区别是什么?

==是抽象相等运算符,而===是严格相等运算符。==运算符是在进行必要的类型转换后,再比较。===运算符不会进行类型转换,所以如果两个值不是相同的类型,会直接返回false。使用==时,可能发生一些特别的事情,例如:

1  ==  '1'; // true1  == [1]; // true1  ==  true; // true0  ==  ''; // true0  ==  '0'; // true0  ==  false; // true

我的建议是从不使用==运算符,除了方便与nullundefined比较时,a == null如果anullundefined将返回true

var a =  null;console.log(a ==  null); // trueconsole.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.callerfunction.arguments

  • 以不同严格模式编写的脚本合并后可能导致问题。

总的来说,我认为利大于弊,我从来不使用严格模式禁用的功能,因此我推荐使用严格模式。

参考

创建一个循环,从1迭代到100,3的倍数时输出 “fizz”,5的倍数时输出 “buzz”,同时为35的倍数时输出 “fizzbuzz”。

来自 Paul Irish的 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构建完成后触发,但不要等待其他资源完成加载。如果在初始化之前不需要装入整个页面,这个事件是使用首选。

TODO.

参考

请解释单页应用是什么,如何使其对SEO友好。

以下摘自 Grab Front End Guide,碰巧的是,这正是我自己写的!

现如今,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。

参考

Promise代替回调函数有什么优缺点?

优点:

  • 避免可读性极差的回调地狱。

  • 使用.then()编写的顺序异步代码,既简单又易读。

  • 使用Promise.all()编写并行异步代码变得很容易。

缺点:

  • 轻微地增加了代码的复杂度(这点存在争议)。

  • 在不支持 ES2015 的旧版浏览器中,需要引入 polyfill 才能使用。

用转译成 JavaScript 的语言写 JavaScript 有什么优缺点?

Some examples of languages that compile to JavaScript include CoffeeScript, Elm, ClojureScript, PureScript andTypeScript. 这些是转译成 JavaScript 的语言,包括 CoffeeScript、Elm、ClojureScript、PureScript 和 TypeScript。

优点:

  • 修复了 JavaScript 中的一些长期问题,并摒弃了 JavaScript 不好的做法。

  • 在 JavaScript 的基础上提供一些语法糖,使我们能够编写更短的代码,我认为 ES5 缺乏语法糖的支持,但ES2015 非常好。

  • 对于需要长时间维护的大型项目,静态类型非常好用(针对 TypeScript)。

缺点:

  • 由于浏览器只运行JavaScript,所以需要构建、编译过程,在将代码提供给浏览器之前,需要将代码转译为JavaScript。

  • 如果 source map 不能很好地映射到预编译的源代码,调试会很痛苦。

  • 大多数开发人员不熟悉这些语言,需要学习它。如果将其用于项目,会增加团队成本。

  • 社区比较小(取决于语言),这意味着资源、教程、图书和工具难以找到。

  • 可能缺乏IDE(编辑器)的支持。

  • 这些语言将始终落后于最新的 JavaScript 标准。

  • 开发人员应该清楚代码正在被编译到什么地方——因为这是实际运行的内容,是最重要的。

实际上,ES2015 已经大大改进了 JavaScript,编写体验很好。我现在还没有真正看到对 CoffeeScript 的需求。

参考

你使用什么工具和技巧调试 JavaScript 代码?

  • React 和 Redux

  • React Devtools

  • Redux Devtools

  • Vue

  • Vue Devtools

  • JavaScript

  • Chrome Devtools

  • debugger声明

  • 使用万金油console.log进行调试

参考

你使用什么语句遍历对象的属性和数组的元素?

对象:

  • for循环:for (var property in obj) { console.log(property); }。但是,这还会遍历到它的继承属性,在使用之前,你需要加入obj.hasOwnProperty(property)检查。

  • Object.keys()Object.keys(obj).forEach(function (property) { ... })Object.keys()方法会返回一个由一个给定对象的自身可枚举属性组成的数组。

  • Object.getOwnPropertyNames()Object.getOwnPropertyNames(obj).forEach(function(property) { ... })Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

数组:

  • for loops:for (var i = 0; i < arr.length; i++)。这里的常见错误是var是函数作用域而不是块级作用域,大多数时候你想要迭代变量在块级作用域中。ES2015 引入了具有块级作用域的let,建议使用它。所以就变成了:for (let i = 0; i < arr.length; i++)

  • forEacharr.forEach(function (el, index) { ... })。这个语句结构有时会更精简,因为如果你所需要的只是数组元素,你不必使用index。还有everysome方法可以让你提前终止遍历。

大多数情况下,我更喜欢.forEach方法,但这取决于你想要做什么。for循环有更强的灵活性,比如使用break提前终止循环,或者递增步数大于一。

请解释可变对象和不可变对象之间的区别。

  • 什么是 JavaScript 中的不可变对象的例子?

  • 不变性有什么优点和缺点?

  • 你如何在自己的代码中实现不变性?

可变对象 在创建之后是可以被改变的。

不可变对象 在创建之后是不可以被改变的。

  1. JavaScript 中,stringnumber 从设计之初就是不可变(Immutable)。

  2. 不可变 其实是保持一个对象状态不变,这样做的好处是使得开发更加简单,可回溯,测试友好,减少了任何可能的副作用。但是,每当你想添加点东西到一个不可变(Immutable)对象里时,它一定是先拷贝已存在的值到新实例里,然后再给新实例添加内容,最后返回新实例。相比可变对象,这势必会有更多内存、计算量消耗。

  3. 比如:构造一个纯函数

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  propertiesconsole.log(student1,  student2);// Object  {school:  "Baidu",  name:  "HOU  Ce",  birthdate:  "1995-12-15"}// Object  {school:  "Baidu",  name:  "YAN  Haijing",  birthdate:  "1990-11-10"}
参考

请解释同步和异步函数之间的区别。

同步函数阻塞,而异步函数不阻塞。在同步函数中,语句完成后,下一句才执行。在这种情况下,程序可以按照语句的顺序进行精确评估,如果其中一个语句需要很长时间,程序的执行会停滞很长时间。

异步函数通常接受回调作为参数,在调用异步函数后立即继续执行下一行。回调函数仅在异步操作完成且调用堆栈为空时调用。诸如从 Web 服务器加载数据或查询数据库等重负载操作应该异步完成,以便主线程可以继续执行其他操作,而不会出现一直阻塞,直到费时操作完成的情况(在浏览器中,界面会卡住)。

什么是事件循环?调用堆栈和任务队列之间有什么区别?

事件循环是一个单线程循环,用于监视调用堆栈并检查是否有工作即将在任务队列中完成。如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行。

如果你没有看过 Philip Robert 关于事件循环的演讲,你应该看一下。这是观看次数最多的 JavaScript 相关视频之一。

参考

请解释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  functionvar  foo  =  function()  { console.log('FOOOOO');  };
参考

使用letvarconst创建变量有什么区别?

var声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,也可以是声明在任何函数外的变量。letconst是块级作用域,意味着它们只能在最近的一组花括号(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  definedconsole.log(baz); // ReferenceError:  baz  is  not  definedconsole.log(qux); // ReferenceError:  qux  is  not  definedif (true)  { var bar =  'bar'; let baz =  'baz'; const  qux  =  'qux';  }// 用  var  声明的变量在函数作用域上都可访问console.log(bar); // bar// let  和  const  定义的变量在它们被定义的语句块之外不可访问console.log(baz); // ReferenceError:  baz  is  not  definedconsole.log(qux); // ReferenceError:  qux  is  not  defined

var会使变量提升,这意味着变量可以在声明之前使用。letconst不会使变量提升,提前使用会报错。

console.log(foo); // undefinedvar foo =  'foo';console.log(baz); // ReferenceError:  can't  access  lexical  declaration  'baz'  before  initializationlet baz =  'baz';console.log(bar); // ReferenceError:  can't  access  lexical  declaration  'bar'  before  initializationconst  bar  =  'bar';

var重复声明不会报错,但letconst会。

var foo =  'foo';var foo =  'bar';console.log(foo); // "bar"let baz =  'baz';let baz =  'qux'; // Uncaught  SyntaxError:  Identifier  'baz'  has  already  been  declared

letconst的区别在于:let允许多次赋值,而const只允许一次。

// 这样不会报错。let foo =  'foo';  foo =  'bar';// 这样会报错。const  baz  =  'baz';  baz =  'qux';
参考

ES6 的类和 ES5 的构造函数有什么区别?

TODO

你能给出一个使用箭头函数的例子吗,箭头函数与其他函数有什么不同?

TODO

在构造函数中使用箭头函数有什么好处?

TODO

高阶函数(higher-order)的定义是什么?

高阶函数是将一个或多个函数作为参数的函数,它用于数据处理,也可能将函数作为返回结果。高阶函数是为了抽象一些重复执行的操作。一个典型的例子是map,它将一个数组和一个函数作为参数。map使用这个函数来转换数组中的每个元素,并返回一个包含转换后元素的新数组。JavaScript 中的其他常见示例是forEachfilterreduce。高阶函数不仅需要操作数组的时候会用到,还有许多函数返回新函数的用例。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); // 3console.log(b); // 1

对象解构

// 变量赋值const  o  = {  p:  42,  q:  true };const { p, q } = o;console.log(p); // 42console.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  1string  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// not  20."
//show函数采用rest参数的写法如下:let name =  '张三',       age =  20,       message = show`我来给大家介绍:${name}的年龄是${age}.`;function  show(stringArr,...values){let output ="";let index =  0  for(;index
参考

你能举出一个柯里化函数(curry function)的例子吗?它有哪些好处?

柯里化(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]
参考

使用扩展运算符(spread)的好处是什么,它与使用剩余参数语句(rest)有什么区别?

在函数泛型编码时,ES6 的扩展运算符非常有用,因为我们可以轻松创建数组和对象的拷贝,而无需使用Object.createslice或其他函数库。这个语言特性在 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 环境中得到支持。

参考

什么情况下会用到静态类成员?

静态类成员(属性或方法)不绑定到某个类的特定实例,不管哪个实例引用它,都具有相同的值。静态属性通常是配置变量,而静态方法通常是纯粹的实用函数,不依赖于实例的状态。

参考

其他答案

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消