前端面试题 — JavaScript(二)

前言

上一篇 前端面试题-JavaScript(一), 感兴趣的小伙伴也可以移步这里查看 完整版JavaScript面试题,面试题会不定期更新加进去一些个人工作中遇到的或者认为比较重要的东西,后面会涉及到前端的各个方面,感兴趣的小伙伴可以关注哦!

如果文章中有出现纰漏、错误之处,还请看到的小伙伴留言指正,先行谢过

以下 ↓

1. 同步和异步的区别,怎么异步加载 JavaScript

同步模式

同步模式,又称阻塞模式。javascript 在默认情况下是会阻塞加载的。当前面的 javascript 请求没有处理和执行完时,会阻止浏览器的后续处理

异步模式

异步加载又叫非阻塞,浏览器在下载执行 js 同时,还会继续进行后续页面的处理

异步加载 JavaScript

  • 动态添加 script 标签
  • defer
  • async

defer属性和
async都是属于
script 标签上面的属性,两者都能实现
JavaScript 的异步加载。不同之处在于:
async 在异步加载完成的时候就马上开始执行了,
defer 会等到
html 加载完毕之后再执行

2. 跨域问题的产生,怎么解决它

由于浏览器的
同源策略,在出现 域名、端口、协议有一种不一致时,就会出现跨域,属于浏览器的一种安全限制。

解决跨域问题有很多种方式,常用的就是以下几种:

  • jsonp 跨域:动态创建script,再请求一个带参网址实现跨域通信.缺点就是只能实现 get 一种请求
  • document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域,就实现了同域.但是仅限主域相同,子域不同的跨域应用场景
  • 跨域资源共享(CORS):只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置
  • nginx反向代理接口跨域:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
  • WebSocket协议跨域

3. 对 this 的理解

JavaScript 中,研究 this 一般都是 this 的指向问题,核心就是 this 永远指向最终调用它的那个对象,除非改变 this 指向或者箭头函数那种特殊情况

function test() {
    console.log(this);
}

test() // window

var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2

// 函数调用的环境不同,所得到的结果也是不一样的

4. apply()、call()和 bind() 是做什么的,它们有什么区别

相同点:三者都可以改变 this 的指向

不同点:

  • apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作为函数参数所组成的数组

var obj = {
    name : 'sss'
}

function func(firstName, lastName){
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.apply(obj, ['A', 'B']);    // A sss B
  • call 方法第一个参数也是作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组
var obj = {
    name: 'sss'
}

function func(firstName, lastName) {
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.call(obj, 'C', 'D');       // C sss D
  • bind 接受的参数有两部分,第一个参数是是作为函数上下文的对象,第二部分参数是个列表,可以接受多个参数
var obj = {
    name: 'sss'
}

function func() {
    console.log(this.name);
}

var func1 = func.bind(null, 'xixi');
func1();

apply
call 方法都会使函数立即执行,因此它们也可以用来调用函数

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window

bind 在传递参数的时候会将自己带过去的参数排在原函数参数之前

function func(a, b, c) {
    console.log(a, b, c);
}
var func1 = func.bind(this, 'xixi');
func1(1,2) // xixi 1 2

5. 什么是内存泄漏,哪些操作会造成内存泄漏

内存泄漏:是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束

可能造成内存泄漏的操作:

  • 意外的全局变量
  • 闭包
  • 循环引用
  • 被遗忘的定时器或者回调函数

你可能还需要知道 垃圾回收机制 此外,高程上面对垃圾回收机制的介绍也很全面,有兴趣的小伙伴可以看看

6. 什么是事件代理,它的原理是什么

事件代理:通俗来说就是将元素的事件委托给它的父级或者更外级元素处理

原理:利用事件冒泡机制实现的

优点:只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有元素都绑定事件,减少内存空间占用,提升性能; 动态新增的元素无需重新绑定事件

7. 对AMD和CMD的理解,它们有什么区别

AMD
CMD都是为了解决浏览器端模块化问题而产生的,
AMD规范对应的库函数有
Require.js
CMD规范是在国内发展起来的,对应的库函数有
Sea.js

AMD和CMD最大的区别是对依赖模块的执行时机处理不同

1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块

2、CMD推崇就近依赖,只有在用到某个模块的时候再去require

参考:AMD-中文版 CMD-规范

8. 对ES6的了解

ECMAScript 6.0 是 JavaScript 语言的下一代标准

新增的特性:

  • 声明变量的方式 let const
  • 变量解构赋值
  • 字符串新增方法 includes() startsWith() endsWith()
  • 数组新增方法 Array.from() Array.of() entries() keys() values()
  • 对象简洁写法以及新增方法 Object.is() Object.assign() entries() keys() values()
  • 箭头函数、rest 参数、函数参数默认值等
  • 新的数据结构: SetMap
  • Proxy
  • Promise对象
  • async函数 await命令
  • Class
  • Module 体系 模块的加载和输出方式

了解更多,参考 ES6入门-阮一峰

9. 箭头函数有什么特点

ES6 允许使用“箭头”(=>)定义函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
}

注意点:

  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误
  • 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替

10. Promise 对象的了解

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大.所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果 –ES6入门-阮一峰

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态

特点:

  • 对象的状态不受外界影响
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  • Promise 新建后就会立即执行
const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
})

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

promise.then(function(value) {
  // success
}, function(error) {
  // failure
})

then 方法返回的是一个新的Promise实例

Promise.prototype.catch 用于指定发生错误时的回调函数,具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误
});

catch 方法返回的还是一个
Promise 对象,因此后面还可以接着调用
then 方法

出去上述方法,Promise还有其他用法,小伙伴们可以在这里查看大佬写的文章 ES6入门-阮一峰

11. async 函数以及 awit 命令

async 函数是什么?一句话,它就是
Generator 函数的语法糖

了解Generator函数的小伙伴,这里 传送门

async 特点:

async 函数返回一个
Promise 对象,可以使用
then 方法添加回调函数。当函数执行的时候,一旦遇到
await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句

async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误

async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

await 命令:
await 命令后面是一个
Promise 对象,返回该对象的结果。如果不是
Promise 对象,就直接返回对应的值

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

await 命令后面是一个
thenable对象(即定义then方法的对象),那么
await会将其等同于
Promise 对象.也就是说就算一个对象不是
Promise对象,但是只要它有
then这个方法,
await 也会将它等同于
Promise对象

使用注意点:

  • await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

了解更多,请点击 这里

12. export 与 export default有什么区别

export
export default 均可用于导出常量、函数、文件、模块等

在一个文件或模块中,exportimport 可以有多个,export default 仅有一个

通过 export 方式导出,在导入时要加 { }export default 则不需要

使用 export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名; export 加载的时候需要知道加载模块的变量名

export default 命令的本质是将后面的值,赋给 default 变量,所以可以直接将一个值写在 export default 之后

13. 前端性能优化

参见 雅虎14条前端性能优化

14. 对JS引擎执行机制的理解

首选明确两点:

JavaScript 是单线程语言

JavaScriptEvent LoopJS 的执行机制, 也就是事件循环

console.log(1)
    
setTimeout(function(){
    console.log(2)
},0)

console.log(3)

// 1 3 2

JavaScript 将任务分为同步任务和异步任务,执行机制就是先执行同步任务,将同步任务加入到主线程,遇到异步任务就先加入到
event table ,当所有的同步任务执行完毕,如果有可执行的异步任务,再将其加入到主线程中执行

视频详解,移步 这里

setTimeout(function(){console.log(1);},0);
new Promise(function(resolve){
     console.log(2);
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log(3)
 });
 
 console.log(4);
 
 // 2 4 3 1

在异步任务中,定时器也属于特殊的存在。有人将其称之为 宏任务、微任务,定时器就属于宏任务的范畴。

参考 JS引擎的执行机制

后记

总结的过程,自己确实也获益颇多,感谢前行的小伙伴。

预祝大家都能找到自己满意的工作

以上

本站声明:网站内容来源于网络,如有侵权,请联系我们,我们将及时处理