2018 – 2019 年前端 JavaScript 面试题


JavaScript 基础问题

1.使以下代码正常运行:

JavaScript 代码:
    const a = [1, 2, 3, 4, 5];
     
    // Implement this
    a.multiply();
     
    console.log(a); // [1, 2, 3, 4, 5, 1, 4, 9, 16, 25]

2.以下代码在 JavaScript 中返回 false 。 解释一下为什么会这样:

JavaScript 代码:
    // false
    0.2 + 0.1 === 0.3

3.JavaScript 中有哪些不同的数据类型?

提示:只有两种类型 – 主要数据类型和引用类型(对象)。 有 6 种主要类型。

4.解决以下异步代码问题。

检索并计算属于同一教室中每个学生的平均分数,其中一些ID为75。每个学生可以在一年内参加一门或多门课程。 以下 API 可用于检索所需数据。

JavaScript 代码:
    // GET LIST OF ALL THE STUDENTS
    GET /api/students
    Response:
    [{
    "id": 1,
    "name": "John",
    "classroomId": 75
    }]
    // GET COURSES FOR GIVEN A STUDENT
    GET /api/courses?filter=studentId eq 1
    Response:
    [{
    "id": "history",
    "studentId": 1
    }, {
    "id": "algebra",
    "studentId": 1
    },]
    // GET EVALUATION FOR EACH COURSE
    GET /api/evaluation/history?filter=studentId eq 1
    Response:
    {
    "id": 200,
    "score": 50,
    "totalScore": 100
    }

编写一个接受教室 ID 的函数,您将根据该函数计算该教室中每个学生的平均值。该函数的最终输出应该是具有平均分数的学生列表:

JavaScript 代码:
    [
    { "id": 1, "name": "John", "average": 70.5 },
    { "id": 3, "name": "Lois", "average": 67 },
    ]

使用普通的 callbacks ,promises ,observablesgenerator 或 async-wait 编写所需的函数。尝试使用至少 3 种不同的技术解决这个问题。

5.使用 JavaScript Proxy 实现简单的数据绑定

提示:ES Proxy 允许您拦截对任何对象属性或方法的调用。首先,每当更改底层绑定对象时,都应更新 DOM 。

6.解释 JavaScript 并发模型

您是否熟悉 Elixir,Clojure,Java 等其他编程语言中使用的任何其他并发模型?

提示:查找事件循环,任务队列,调用栈,堆等。

7.new 关键字在 JavaScript 中有什么作用?

提示:在 JavaScript 中,new 是用于实例化对象的运算符。 这里的目的是了解知识广度和记忆情况。

另外,请注意 [[Construct]] 和 [[Call]]

8.JavaScript 中有哪些不同的函数调用模式? 详细解释。

提示:有四种模式,函数调用,方法调用,.call() 和 .apply()

9.解释任一即将发布新的 ECMAScript 提案。

提示:比如 2018 的 BigInt,partial 函数,pipeline 操作符 等。

10.JavaScript 中的迭代器(iterators)和迭代(iterables)是什么? 你知道什么是内置迭代器吗?

11.为什么 JavaScript classes(类)被认为是坏的或反模式?

这是一个神话吗?它是否遭受了误传?是否有一些有用的用例?

12.如何在 JSON 中序列化以下对象?

如果我们将以下对象转换为 JSON 字符串,会发生什么?

JavaScript 代码:
    const a = {
    key1: Symbol(),
    key2: 10
    }
    // What will happen?
    console.log(JSON.stringify(a));

13.你熟悉 Typed Arrays 吗? 如果熟悉,请解释他们与 JavaScript 中的传统数组相比的异同?

Arrays

ES6对数组添加了一些新的方法,另外还添加了TypedArray类型,这种类型支持对内存的操作,ArrayBuffer和C语言内存分配一样,分配一块内存块。下面从以下几个方面来看看ES6数组的变化:

  1. 2个静态方法 Array.of(), Array.from();
  2. 数组原型上新添加的方法 find(), findIndex(), fill(), copyWithin();
  3. 新类型 ArrayBuffer;
  4. Typed Arrays, 以及和Array的相同性,差异性

一.Array.of() & Array.from()

1.Array.of()

ES6新添加了这个静态方法,用于数组构造器实例化数组。我们知道数组实例化一般可以通过构造器或者数组字面量的形式来声明。ES5通过构造器声明数组会出现一个比较模糊的地方,比如:

var arr = new Array(1, 2, 3);
// 表示声明一个数组元素为[1, 2, 3]的数组
arr.length; // 3
arr[0]; // 1
arr[1]; // 2
arr[2]; // 3

var arr = new Array(2); // 2表示长度
// 而这样则表示一个数组长度为2的数组,而数组元素未声明
arr.length; // 2
arr; // [undefined, undefined]

Array.of()消除了这种模糊,凡是向方法中添加数字,都表示数组元素,而不是长度

var arr = Array.of(1, 2);
arr; // [1, 2]

var arr = Array.of(2); // 2表示元素
arr; // [2]

2.Array.from()

ES6之前要将一个array-like对象转换成数组,我们一般是利用slice方法,比如

function doSomething() {

    // 将arguments类数组对象转换成数组
    var args = Array.prototype.slice.call(arguments);
    // 或者  [].slice.call(arguments)
    // ...

}

ES6通过静态方法 Array.from() 可以将 类数组对象 或者 可迭代的对象 转换成一个数组,其语法为:

Array.from(arraylike[, callback] [, thisArg])

上面的例子可以写为:

function doSomething() {
    var args = Array.from(arguments);
    // ...
}

将可迭代的对象转变为数组:

var set = new Set([1, 2, 2, 4, 5]);
// Set {1, 2, 4, 5}
var arr = Array.from(set); // [1, 2, 4, 5]

后面添加回调函数, 如果回调函数属于一个对象, 则可以添加第3个可选参数,指出this上下文:

let helper = {
    diff: 1,
    add(value) {
        return value + this.diff;
    }
}
 
function translate() {
    // 第2个参数为callback, 第3个参数为上下文
    return Array.from(arguments, helper.add, helper);
}

translate(1, 2, 3); // [2, 3, 4]

二.新添加的方法

1.find(),findIndex()

以前我们查看数组中是否存在某个值或者某个值的位置时,一般使用indexOf(), lastIndexOf(),ES6添加了find(), findIndex()来添加条件查找。这两个方法和map(),forEach()一样添加一个回调函数,有选择性的添加thisArg指定上下文。

find找到了就返回第一个满足条件的,未找到返回undefined, findIndex返回索引位置,未找到返回 -1:

var arr = [1, 2, 19, 16];
arr.find(v => v > 10 );  // 返回 19
arr.findIndex(v => v > 10); // 返回 2

find(), findIndex()用于查找一个数组元素满足某个条件而不是值,要根据值查找建议使用indexOf(), lastIndexOf().

2.fill(), copyWithin()

这两个方法其实为了操作Typed Array对象使用的,但是为了保持一致性,也添加给普通数组了。看下语法:

    fill(value[,startFillPostion = 0 ] [, endFillPostion = arr.length])
    copyWithin(StartToBeCopiedPos[,StartCopyPos = 0] [,EndCopyPos = arr.length])

先看fill:

var arr = [1, 2, 3, 4];

// 不指定开始和结束填充的位置
arr.fill(5); // arr: [5, 5, 5, 5]

// 指定开始
arr.fill(5, 2); // arr: [1, 2, 5, 5]

// 都指定,不包含结束位置
arr.fill(5, 0, 2)// arr: [5, 5, 3, 4]

// 当然起始和结尾也可以为负数,相当于加上数组长度
arr.fill(5, -3); // arr: [1, 5, 5, 5]
// 相当于 arr.fill(5, -3+4)

copyWith: 比较绕, 它是指复制自身内容到指定的位置:

var arr = [1, 10, 15, 29, 18];

// 只有一个参数表示被复制的索引,另外2个参数则默认从0开始到结束
arr.copyWithin(2); // arr [1, 10, 1, 10, 15]

// 2个参数,指定自身复制的位置
arr.copyWithin(3, 1); // arr [1, 10, 15, 10, 15]

// 3个参数都指定
arr.copyWithin(2, 0, 1); // arr [1, 10, 1, 29, 18]
// 0-1只有一个数 "1", 所有索引位置为2的 "15" 被替换成 "1"

上面例子我们可以发现,是有这些方法都会改变数组自身


三.ArrayBuffer

AarryBuffer是指分配内存中的一块位置用于操作,相当于C语言中的malloc(),对内存块进行二进制操作,会极大的提升性能,满足一些特别的接口要求。
先了解一下内存分配的基本语法:

var buffer = new ArrayBuffer(bytes);

比如:分配10个字节(byte)

var buffer = new ArrayBuffer(10);

内存的大小确定之后是不能修改的,可以改变内部内容

属性: byteLength , slice()
slice方法是从已经存在的内存块中复制一段,添加都新的内存块中

var buffer = new ArrayBuffer(10);
var buffer2 = buffer.slice(3, 5);
// 将buffer中的3, 4字节内容拷贝到新的内存块中
 console.log(buffer2.byteLength); // 2

四.TypedArray, Views视图

光有内存块,而不进行操作也是没有用的,javascript通过视图的方式对内存块进行读写,存在两种视图:

  1. TypedArray: 特定类型的数据类型,特定类型的一种视图,对特定类型操作性能更高;
  2. DataView: 各种数据类型都可以,还可以指定大端序(BIG_ENDIAN),小端序(LITTLE_ENDIAN),功能更强大的一种视图

1.共同属性

这两种视图拥有一些共同的属性:

  • buffer: 表示指向的内存块;

2.DataView

DataView构造器能够添加三个参数:new DataView(buffer[, byteOffset][, byteLength])

var buffer = new ArrayBuffer(10);
// 指向整个内存块
var dataView1 = new DataView(buffer);    
dataView1.buffer === buffer; // true
dataView1.byteOffset; // 0
dataView1.byteLength; // 10

// 表示 字节 5, 6上的视图
var dataView2 = new DataView(buffer, 5, 2);
dataView2.buffer === buffer; // true
dataView2.byteOffset; // 5
dataView2.byteLength; // 2

3.TypedArray

TypedArray本质上是一个抽象类,他表示9中特定类型: Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
Float64Array,还有一种只针对Canvas颜色值的 Uint8ClampedArray

14. 默认参数是如何工作?

如果我们在调用 makeAPIRequest 函数时必须使用 timeout 的默认值,那么正确的语法是什么?

JavaScript 代码:
    function makeAPIRequest(url, timeout = 2000, headers) {
    // Some code to fetch data
    }

15.解释 TCO – 尾调用优化(Tail Call Optimization)。 有没有支持尾调用优化的 JavaScript 引擎?

  • 尾调用指“函数尾调用”。
  • 尾调用是函数式编程的一个重要概念。
  • 尾调用就是某个函数最后一步是调用另一个函数

提示:截至 2018 年,没有。


JavaScript 前端应用设计问题

1.解释单向数据流和双向数据绑定。

Angular 1.x 基于双向数据绑定,而 React,Vue,Elm 等基于单向数据流架构。

单向数据绑定

单向数据绑定,带来单向数据流。。
指的是我们先把模板写好,然后把模板和数据(数据可能来自后台)整合到一起形成HTML代码,然后把这段HTML代码插入到文档流里面。适用于整体项目,并于追溯。

 

优点:
1. 所有状态的改变可记录、可跟踪,源头易追溯;
2. 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性;
3. 一旦数据变化,就去更新页面(data-页面),但是没有(页面-data);
4. 如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中。
方神:双向绑定 = 单向绑定 + UI事件监听,可了解vuex
缺点:
1. HTML代码渲染完成,无法改变,有新数据,就须把旧HTML代码去掉,整合新数据和模板重新渲染;
2. 代码量上升,数据流转过程变长,出现很多类似的样板代码;
3. 同时由于对应用状态独立管理的严格要求(单一的全局store),在处理局部状态较多的场景时(如用户输入交互较多的“富表单型”应用),会显得啰嗦及繁琐。

双向数据绑定

双向数据绑定,带来双向数据流。AngularJS2添加了单向数据绑定
数据模型(Module)和视图(View)之间的双向绑定。无论数据改变,或是用户操作,都能带来互相的变动,自动更新。适用于项目细节,如:UI控件中(通常是类表单操作)。


优点:
1. 用户在视图上的修改会自动同步到数据模型中去,数据模型中值的变化也会立刻同步到视图中去;
2. 无需进行和单向数据绑定的那些CRUD(Create,Retrieve,Update,Delete)操作;
3. 在表单交互较多的场景下,会简化大量业务无关的代码。
缺点:
1. 无法追踪局部状态的变化;
2. “暗箱操作”,增加了出错时 debug 的难度;
3. 由于组件数据变化来源入口变得可能不止一个,数据流转方向易紊乱,若再缺乏“管制”手段,血崩。
双向数据绑定,Angular使用脏检查“digest” - “dirty checking”
(在angular中,他没有办法判断你的数据是否做了更改, 所以它设置了一些条件,当你触发了这些条件之后,它就执行一个检测来遍历所有的数据,对比你更改了地方,然后执行变化。这个检查很不科学。而且效率不高,有很多多余的地方,所以官方称为脏检查)

2.单向数据流架构在哪些方面适合 MVC?

MVC 拥有大约 50 年的悠久历史,并已演变为 MVP,MVVM 和 MV *。两者之间的相互关系是什么?如果 MVC 是架构模式,那么单向数据流是什么?这些竞争模式是否能解决同样的问题?

3.客户端 MVC 与服务器端或经典 MVC 有何不同?

提示:经典 MVC 是适用于桌面应用程序的 Smalltalk MVC。在 Web 应用中,至少有两个不同的数据 MVC 周期。

4.使函数式编程与面向对象或命令式编程不同的关键因素是什么?

提示:Currying(柯里化),point-free 函数,partial 函数应用,高阶函数,纯函数,独立副作用,record 类型(联合,代数数据类型)等。

5.在 JavaScript 和前端的上下文中,函数式编程与响应式编程有什么关系?

提示:没有正确答案。但粗略地说,函数式编程是关于小型编码,编写纯函数和响应式编程是大型编码,即模块之间的数据流,连接以 FP 风格编写的组件。 FRP – 功能响应式编程( Functional Reactive Programming)是另一个不同但相关的概念。

6.不可变数据结构(immutable data structures)解决了哪些问题?

不可变结构是否有任何性能影响? JS 生态系统中哪些库提供了不可变的数据结构?这些库的优点和缺点是什么?

提示:线程安全(我们真的需要在浏览器 JavaScript 中担心吗?),无副作用的函数,更好的状态管理等。

7.大型应用程序是否应使用静态类型?

  1. 如何比较 TypeScript/Flow 与 Elm/ReasonML/PureScript 等 JS 转换语言?这些方法的优缺点是什么?
  2. 选择特定类型系统的主要标准应该是什么?
  3. 什么是类型推断(type inference)?
  4. 静态类型语言和强类型语言有什么区别?在这方面 JavaScript 的本质是什么?
  5. 有你知道的弱类型但静态类型的语言吗?有你知道的动态类型但强类型的语言吗?举例一二。

提示:Structural 与 Nominal 类型系统,类型稳健性,工具/生态系统支持,正确性超过方便。

8.JavaScript 中有哪些杰出的模块系统(module systems )?如何评价 ES 模块系统。

列出在实现不同模块系统之间互操作所涉及的一些复杂性问题(主要对 ES 模块和 CommonJS 互操作感兴趣)

9.HTTP/2 将如何影响 JavaScript 应用程序打包?

列出 HTTP/2 与其上一个版本的基本区别。

10.Fetch API 相对于传统的 Ajax 有哪些改进?

  1. 使用 Fetch API 有那些缺点/难点吗?
  2. 哪些是Ajax 可以做的,而 fetch 不能做的?

11.如何评论 pull-based 和 push-based 的反应系统。

讨论概念,含义,用途等。

  1. 在这个讨论中加入惰性和及早求值。
  2. 然后在讨论中添加单数和复数值维度。
  3. 最后,讨论值解析的同步和异步性质。
  4. 为JavaScript中可用的每个组合提供示例。

提示:Observable 是惰性的,基于推送的复数值构造,同时具有 async/sync 调度程序。

12.讨论与 Promise 相关的问题。

提示:及早求值(eager evaluation),尴尬的取消机制,用 then() 方法伪装 map() 和 flatMap() 等。


前端基础和理论问题

1.HTML 中 Doctype 的用途是什么?

具体谈谈,以下每种情况下会发生什么:

    1. Doctype 不存在。
    2. 使用了 HTML4 Doctype,但 HTML 页面使用了 HTML5 的标签,如  或  。它会导致任何错误吗?
    3. 使用了无效的 Doctype。

1、告诉浏览器使用什么样的html或xhtml规范来解析html文档

2、对浏览器的渲染模式产生影响;不同的渲染模式会影响到浏览器对于 CSS 代码甚至 JavaScript 脚本的解析,所以Doctype是非常关键的,尤其是在 IE 系列浏览器中,由DOCTYPE 所决定的 HTML 页面的渲染模式至关重要。 

两种渲染模式:

     BackCompat:标准兼容模式未开启(或叫怪异模式[Quirks mode]、混杂模式)

     CSS1Compat:标准兼容模式已开启(或叫严格模式[Standards mode/Strict mode])

选择什么样的DOCTYPE

如上例所示,XHTML 1.0中有3种DTD(文档类型定义)声明可以选择:过渡的(Transitional)、严格的(Strict)和框架的(Frameset)。这里分别介绍如下。

1.过渡的

一种要求不很严格的DTD,允许在页面中使用HTML4.01的标识(符合xhtml语法标准)。过渡的DTD的写法如下:

代码如下:

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

2.严格的

一种要求严格的DTD,不允许使用任何表现层的标识和属性,例如
等。严格的DTD的写法如下:

代码如下:

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

3.框架的

一种专门针对框架页面所使用的DTD,当页面中含有框架元素时,就要采用这种DTD。框架的DTD的写法如下:

代码如下:

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

使用严格的DTD来制作页面,当然是最理想的方式。但是,对于没有深入了解Web标准的网页设计者,比较合适的是使用过渡的DTD。因为这种DTD还允许使用表现层的标识、元素和属性,比较适合大多数网页制作人员。

HTML 4.01 中的 doctype 需要对 DTD 进行引用,因为 HTML 4.01 基于 SGML。
HTML 5 不基于 SGML,因此不需要对 DTD 进行引用,但是需要 doctype 来规范浏览器的行为(html 5简化了这种声明,意在告诉浏览器使用统一的标准即可)

2. DOM 和 BOM 的区别是什么?

提示:BOM,DOM,ECMAScript 和 JavaScript 都是不同的东西。

BOM和浏览器关系密切,DOM和HTML文档有关。BOM是Browser Object Mode的缩写,及对浏览器对象模型,用来获取或设置浏览器的属性、行为。DOM是Document Object Model 的缩写,即文档对象模型,用来获取或设置文档中标签的属性。BOM没有相关标准。DOM是W3C的标准。BOM的最根本对象是window。DOM最根本对象是document(实际上是window.document)。由于DOM的操作对象是文档(Document),所以dom和浏览器没有直接关系。 
HTML DOM 模型被构造为对象的树。
通过可编程的对象模型,JavaScript 获得了足够的能力来创建动态的 HTML。
JavaScript 能够改变页面中的所有 HTML 元素
JavaScript 能够改变页面中的所有 HTML 属性
JavaScript 能够改变页面中的所有 CSS 样式
JavaScript 能够对页面中的所有事件做出反应

3.JavaScript 中的事件处理如何运行?

如下图所示,我们有三个 div 元素。每个 div 都有一个与之关联的点击处理程序。处理程序执行以下任务:

  • Outer div click 处理程序将 hello outer 打印到控制台。
  • Inner div click 处理程序将 hello inner 打印到控制台。
  • Innermost div click 处理程序将 hello innermost 打印到控制台。

编写一段代码来分配这些任务,以便在单击 innermost div 时始终打印以下序列?

hello inner → hello innermost → hello outer

事件冒泡和捕获

提示:事件捕获和事件冒泡

探究JavaScript中的五种事件处理程序

  我们知道JavaScript与HTML之间的交互是通过事件实现的。事件最早是在IE3和Netscape Navigator 2中出现的,当时是作为分担服务器运算负载的一种手段。  通俗地理解,事件就是用户或浏览器自身执行的某种操作。而事件处理程序即为响应某个事件的函数。抽出主干,即事件处理程序为函数。  我们又把事件处理程序称为事件侦听器。  事件处理程序是以"on"开头的,因此对于事件on的时间处理程序即为onclick。时间处理程序在JavaScript中大致有五种,下面会根据这五种不同的时间处理程序分为5部分来介绍。

  1. HTML事件处理程序
  2. DOM0级事件处理程序
  3. DOM2级事件处理程序
  4. IE事件处理程序
  5. 跨浏览器的事件处理程序

JavaScript中的两种事件流


下面通过两个例子加深理解:

例7:



结果:第一次弹出窗口:button。

   第二次弹出窗口:another event。

结论通过DOM2级事件处理程序,我们可以为同一个元素添加两个或更多的事件。事件根据顺序依次触发。且this同样指向当前元素,故函数在元素的作用域中执行。

this分析:和前面的DOM0级事件处理程序一样,这里的addEventListener同样也可以看作对象的方法,不同之初在于,DOM0级的方法需要另外一个函数来赋值,而这里的方法是DOM2级规范预定义的。

 

  removeEventListener()这个删除事件处理程序的方法值得注意的是:使用addEventListener()来添加的事件处理程序只能通过它来移除,且需要传入相同的参数。

例8:

    

  成功移除!

注意:1.传入方法的handler没有(),是因为这里都只是定义函数,而不是调用,需要注意。

   2.这两个方法的第三个参数都是false,即事件处理程序添加到冒泡阶段。一般不使用true,因为低版本的IE不支持捕获阶段。

 

DOM2级事件处理程序成功地解决了前面所有事件处理程序的问题,堪称perfect!!!!  然而总是特立独行的IE浏览器又有新花样,它也有自己的一套事件处理程序,下面我们就来看看吧。

XMLHttpRequest是一个浏览器接口,使得Javascript可以进行HTTP(S)通信。

最早,微软在IE 5引进了这个接口。因为它太有用,其他浏览器也模仿部署了,ajax操作因此得以诞生。

但是,这个接口一直没有标准化,每家浏览器的实现或多或少有点不同。HTML 5的概念形成后,W3C开始考虑标准化这个接口。2008年2月,就提出了XMLHttpRequest Level 2 草案。

这个XMLHttpRequest的新版本,提出了很多有用的新功能,将大大推动互联网革新。本文就对这个新版本进行详细介绍。

一、老版本的XMLHttpRequest对象

在介绍新版本之前,我们先回顾一下老版本的用法。

首先,新建一个XMLHttpRequest的实例。

  var xhr = new XMLHttpRequest();

然后,向远程主机发出一个HTTP请求。

  xhr.open('GET', 'example.php');

  xhr.send();

接着,就等待远程主机做出回应。这时需要监控XMLHttpRequest对象的状态变化,指定回调函数。

  xhr.onreadystatechange = function(){

    if ( xhr.readyState == 4 && xhr.status == 200 ) {

      alert( xhr.responseText );

    } else {

      alert( xhr.statusText );

    }

  };

上面的代码包含了老版本XMLHttpRequest对象的主要属性:

  * xhr.readyState:XMLHttpRequest对象的状态,等于4表示数据已经接收完毕。

  * xhr.status:服务器返回的状态码,等于200表示一切正常。

  * xhr.responseText:服务器返回的文本数据

  * xhr.responseXML:服务器返回的XML格式的数据

  * xhr.statusText:服务器返回的状态文本。

二、老版本的缺点

老版本的XMLHttpRequest对象有以下几个缺点:

  * 只支持文本数据的传送,无法用来读取和上传二进制文件。

  * 传送和接收数据时,没有进度信息,只能提示有没有完成。

  * 受到"同域限制"(Same Origin Policy),只能向同一域名的服务器请求数据。

三、新版本的功能

新版本的XMLHttpRequest对象,针对老版本的缺点,做出了大幅改进。

  * 可以设置HTTP请求的时限。

  * 可以使用FormData对象管理表单数据。

  * 可以上传文件。

  * 可以请求不同域名下的数据(跨域请求)。

  * 可以获取服务器端的二进制数据。

  * 可以获得数据传输的进度信息。

下面,我就一一介绍这些新功能。

四、HTTP请求的时限

有时,ajax操作很耗时,而且无法预知要花多少时间。如果网速很慢,用户可能要等很久。

新版本的XMLHttpRequest对象,增加了timeout属性,可以设置HTTP请求的时限。

  xhr.timeout = 3000;

上面的语句,将最长等待时间设为3000毫秒。过了这个时限,就自动停止HTTP请求。与之配套的还有一个timeout事件,用来指定回调函数。

  xhr.ontimeout = function(event){

    alert('请求超时!');

  }

目前,Opera、Firefox和IE 10支持该属性,IE 8和IE 9的这个属性属于XDomainRequest对象,而Chrome和Safari还不支持。

五、FormData对象

ajax操作往往用来传递表单数据。为了方便表单处理,HTML 5新增了一个FormData对象,可以模拟表单。

首先,新建一个FormData对象。

  var formData = new FormData();

然后,为它添加表单项。

  formData.append('username', '张三');

  formData.append('id', 123456);

最后,直接传送这个FormData对象。这与提交网页表单的效果,完全一样。

  xhr.send(formData);

FormData对象也可以用来获取网页表单的值。

  var form = document.getElementById('myform');

  var formData = new FormData(form);

  formData.append('secret', '123456'); // 添加一个表单项

  xhr.open('POST', form.action);

  xhr.send(formData);

六、上传文件

新版XMLHttpRequest对象,不仅可以发送文本信息,还可以上传文件。

假定files是一个"选择文件"的表单元素(input[type="file"]),我们将它装入FormData对象。

  var formData = new FormData();

  for (var i = 0; i < files.length;i++) {

    formData.append('files[]', files[i]);

  }

然后,发送这个FormData对象。

  xhr.send(formData);

七、跨域资源共享(CORS)

新版本的XMLHttpRequest对象,可以向不同域名的服务器发出HTTP请求。这叫做"跨域资源共享"(Cross-origin resource sharing,简称CORS)。

使用"跨域资源共享"的前提,是浏览器必须支持这个功能,而且服务器端必须同意这种"跨域"。如果能够满足上面的条件,则代码的写法与不跨域的请求完全一样。

  xhr.open('GET', 'http://other.server/and/path/to/script');

目前,除了IE 8和IE 9,主流浏览器都支持CORS,IE 10也将支持这个功能。服务器端的设置,请参考《Server-Side Access Control》

八、接收二进制数据(方法A:改写MIMEType)

老版本的XMLHttpRequest对象,只能从服务器取回文本数据(否则它的名字就不用XML起首了),新版则可以取回二进制数据。

这里又分成两种做法。较老的做法是改写数据的MIMEType,将服务器返回的二进制数据伪装成文本数据,并且告诉浏览器这是用户自定义的字符集。

  xhr.overrideMimeType("text/plain; charset=x-user-defined");

然后,用responseText属性接收服务器返回的二进制数据。

  var binStr = xhr.responseText;

由于这时,浏览器把它当做文本数据,所以还必须再一个个字节地还原成二进制数据。

  for (var i = 0, len = binStr.length; i < len; ++i) {

    var c = binStr.charCodeAt(i);

    var byte = c & 0xff;

  }

最后一行的位运算"c & 0xff",表示在每个字符的两个字节之中,只保留后一个字节,将前一个字节扔掉。原因是浏览器解读字符的时候,会把字符自动解读成Unicode的0xF700-0xF7ff区段。

八、接收二进制数据(方法B:responseType属性)

从服务器取回二进制数据,较新的方法是使用新增的responseType属性。如果服务器返回文本数据,这个属性的值是"TEXT",这是默认值。较新的浏览器还支持其他值,也就是说,可以接收其他格式的数据。

你可以把responseType设为blob,表示服务器传回的是二进制对象。

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = 'blob';

接收数据的时候,用浏览器自带的Blob对象即可。

  var blob = new Blob([xhr.response], {type: 'image/png'});

注意,是读取xhr.response,而不是xhr.responseText。

你还可以将responseType设为arraybuffer,把二进制数据装在一个数组里。

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = "arraybuffer";

接收数据的时候,需要遍历这个数组。

  var arrayBuffer = xhr.response;

  if (arrayBuffer) {

    var byteArray = new Uint8Array(arrayBuffer);

    for (var i = 0; i < byteArray.byteLength; i++) {

      // do something

    }
  }

更详细的讨论,请看Sending and Receiving Binary Data

九、进度信息

新版本的XMLHttpRequest对象,传送数据的时候,有一个progress事件,用来返回进度信息。

它分成上传和下载两种情况。下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。

我们先定义progress事件的回调函数。

  xhr.onprogress = updateProgress;

  xhr.upload.onprogress = updateProgress;

然后,在回调函数里面,使用这个事件的一些属性。

  function updateProgress(event) {

    if (event.lengthComputable) {

      var percentComplete = event.loaded / event.total;

    }

  }

上面的代码中,event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0。

与progress事件相关的,还有其他五个事件,可以分别指定回调函数:

  * load事件:传输成功完成。

  * abort事件:传输被用户取消。

  * error事件:传输中出现错误。

  * loadstart事件:传输开始。

  * loadEnd事件:传输结束,但是不知道成功还是失败。

二、fetch(non-streaming)

与XMLHttpRequest(XHR)类似,fetch()方法允许你发出AJAX请求。区别在于Fetch API使用Promise,因此是一种简洁明了的API,比XMLHttpRequest更加简单易用。

从Chrome 40开始,Fetch API可以被利用在Service Worker全局作用范围中,自Chrome 42开始,可以被利用在页面中。

如果你还不了解Promise,需要首先补充这方面知识。

网格线(Grid Lines)
  • 网格轨道(Grid Track)
  • 网格单元格(Grid Cell)
  • 网格区域(Grid Area)
  • 网格容器(Grid Containers)
  • 网格单元格顺序(order)
  • 浮动元素(float: left | right;)如何在 CSS Grid 和 flexbox 中渲染?
  • 提示:等高的列,垂直居中,复杂网格等。

    10.什么时候应该使用 CSS animations 而不是 CSS transitions ?你做出这个决定标准是什么?

    11.如果你正在 Review CSS 代码,那么你在代码中经常遇到的问题是什么?

    示例:使用魔性数字,如 width: 67px; 或使用 em 代替 rem 单位,在通用代码之前编写 media queries(媒体查询),滥用 ID 和类等。

    12.如何在 JavaScript 中检测触摸事件?

    有三种情况,1为触摸鼠标都可以使用 2为触摸 3为鼠标

    if((document.hasOwnProperty("ontouchstart")) && (document.hasOwnProperty("onmousedown"))){
    }else if(document.body.ontouchstart !== undefined){
     
    }else{
     
    }
    // document.body.ontouchstart !== undefined也是特性检测的一种和那位兄弟的代码差不多

    mouse 事件是所有浏览器都支持的,一款普通的触屏手机也可以通过 USB OTG 外接鼠标。

    所以你只能判断浏览器是否支持触屏,这里的关键就是

    【ontouchstart,ontouchmove,ontouchend,ontouchcancel】

    支持触屏的浏览器都会有这四个 touch 事件

    window.onload = function() {
        if(document.hasOwnProperty("ontouchstart")) {
            alert("浏览器支持触屏");
        }
        else {
            alert("浏览器不支持触屏");
        }
    };
    1. 你是否不看好检测设备对触摸事件的支持?如果是,为什么?
    2. 主流做法一般是通过 UserAgent 判断。
    3. 比较触摸事件和点击事件。
    4. 在触摸设备上,touch事件从手指刚碰到屏幕就会触发,而click事件则要晚一会儿才被触发。触发顺序如下:

      touchstart
      mouseover
      mousemove(一次)
      mousedown
      mouseup
      click
      touchend
      所以,如果想提升web在触摸设备下的用户体验,让用户觉得响应非常迅速,应该对页面的触摸事件进行事件处理程序的注册,而不应再关注click事件。

    5. 当设备同时支持触摸和鼠标事件时,你认为这些事件的正确事件顺序是什么或应该是什么?
    • touchstart
    • Zero or more touchmove events, depending on movement of the finger(s)
    • touchend
    • mousemove
    • mousedown
    • mouseup
    • click

    13.为 script 标签定义的 async 和 defer 属性有什么用?

    • 现在我们有 HTTP/2 和 ES 模块,它们真的很有用吗?
    • 向html页面中插入javascript代码的主要方法就是通过script标签。其中包括两种形式,第一种直接在script标签之间插入js代码,第二种即是通过src属性引入外部js文件。由于解释器在解析执行js代码期间会阻塞页面其余部分的渲染,对于存在大量js代码的页面来说会导致浏览器出现长时间的空白和延迟,为了避免这个问题,建议把全部的js引用放在标签之前。

        script标签存在两个属性,defer和async,因此script标签的使用分为三种情况:

        1.

         没有defer或async属性,浏览器会立即加载并执行相应的脚本。也就是说在渲染script标签之后的文档之前,不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载;

        2.

           有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;

        3.

               有了defer属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是并行进行的(异步),js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。

        下图可以直观的看出三者之间的区别:

       

        

        其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

        从图中我们可以明确一下几点:

        1.defer和async在网络加载过程是一致的,都是异步执行的;

        2.两者的区别在于脚本加载完成之后何时执行,可以看出defer更符合大多数场景对应用脚本加载和执行的要求;

        3.如果存在多个有defer属性的脚本,那么它们是按照加载顺序执行脚本的;而对于async,它的加载和执行是紧紧挨着的,无论声明顺序如何,只要加载完成就立刻执行,它对于应用脚本用处不大,因为它完全不考虑依赖。