2018 – 2019 年前端 JavaScript 面试题
JavaScript 基础问题
1.使以下代码正常运行:
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
。 解释一下为什么会这样:
// false 0.2 + 0.1 === 0.3
3.JavaScript 中有哪些不同的数据类型?
提示:只有两种类型 – 主要数据类型和引用类型(对象)。 有 6 种主要类型。
4.解决以下异步代码问题。
检索并计算属于同一教室中每个学生的平均分数,其中一些ID为75。每个学生可以在一年内参加一门或多门课程。 以下 API 可用于检索所需数据。
// 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 的函数,您将根据该函数计算该教室中每个学生的平均值。该函数的最终输出应该是具有平均分数的学生列表:
[ { "id": 1, "name": "John", "average": 70.5 }, { "id": 3, "name": "Lois", "average": 67 }, ]
使用普通的 callbacks ,promises ,observables,generator 或 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 字符串,会发生什么?
const a = { key1: Symbol(), key2: 10 } // What will happen? console.log(JSON.stringify(a));
13.你熟悉 Typed Arrays 吗? 如果熟悉,请解释他们与 JavaScript 中的传统数组相比的异同?
Arrays
ES6对数组添加了一些新的方法,另外还添加了TypedArray类型,这种类型支持对内存的操作,ArrayBuffer
和C语言内存分配一样,分配一块内存块。下面从以下几个方面来看看ES6数组的变化:
- 2个静态方法
Array.of()
,Array.from()
;
- 数组原型上新添加的方法
find()
,findIndex()
,fill()
,copyWithin()
;
- 新类型
ArrayBuffer
;
- 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通过视图的方式对内存块进行读写,存在两种视图:
- TypedArray: 特定类型的数据类型,特定类型的一种视图,对特定类型操作性能更高;
- 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
的默认值,那么正确的语法是什么?
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.大型应用程序是否应使用静态类型?
- 如何比较 TypeScript/Flow 与 Elm/ReasonML/PureScript 等 JS 转换语言?这些方法的优缺点是什么?
- 选择特定类型系统的主要标准应该是什么?
- 什么是类型推断(type inference)?
- 静态类型语言和强类型语言有什么区别?在这方面 JavaScript 的本质是什么?
- 有你知道的弱类型但静态类型的语言吗?有你知道的动态类型但强类型的语言吗?举例一二。
提示:Structural 与 Nominal 类型系统,类型稳健性,工具/生态系统支持,正确性超过方便。
8.JavaScript 中有哪些杰出的模块系统(module systems )?如何评价 ES 模块系统。
列出在实现不同模块系统之间互操作所涉及的一些复杂性问题(主要对 ES 模块和 CommonJS 互操作感兴趣)
9.HTTP/2 将如何影响 JavaScript 应用程序打包?
列出 HTTP/2 与其上一个版本的基本区别。
10.Fetch API 相对于传统的 Ajax 有哪些改进?
- 使用 Fetch API 有那些缺点/难点吗?
- 哪些是Ajax 可以做的,而 fetch 不能做的?
11.如何评论 pull-based 和 push-based 的反应系统。
讨论概念,含义,用途等。
- 在这个讨论中加入惰性和及早求值。
- 然后在讨论中添加单数和复数值维度。
- 最后,讨论值解析的同步和异步性质。
- 为JavaScript中可用的每个组合提供示例。
提示:Observable 是惰性的,基于推送的复数值构造,同时具有 async/sync 调度程序。
12.讨论与 Promise 相关的问题。
提示:及早求值(eager evaluation),尴尬的取消机制,用 then()
方法伪装 map()
和 flatMap()
等。
前端基础和理论问题
1.HTML 中 Doctype 的用途是什么?
具体谈谈,以下每种情况下会发生什么:
- Doctype 不存在。
- 使用了 HTML4 Doctype,但 HTML 页面使用了 HTML5 的标签,如
<audio>
或<video>
。它会导致任何错误吗? - 使用了无效的 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的写法如下:
代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2.严格的
一种要求严格的DTD,不允许使用任何表现层的标识和属性,例如<br/>等。严格的DTD的写法如下:
代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3.框架的
一种专门针对框架页面所使用的DTD,当页面中含有框架元素时,就要采用这种DTD。框架的DTD的写法如下:
代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""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部分来介绍。
第一部分:HTML事件处理程序
什么使HTML事件处理程序呢?显然,通过名字就可以猜到,它是卸载HTML中的函数(事件处理程序)。初学者大多用到的事件处理程序即为HTML事件处理程序。下面举例:
例1:
<button onclick="alert('success')">点我</button>
这条代码即为事件处理程序,点击button后,会弹出弹框,显示success。
特点:HTML事件处理程序中Javascript代码作为了onclick特性的值,因此,我们不能在JavaScript代码中使用未经转义的HTML语法字符,如&(和号)、""(双引号)、<(小于号)、>(大于号)等等。所以这个例子中字符串我使用了单引号而没有使用双引号。看下面在JavaScript代码中使用了未经转义的HTML语法字符。
例2:
<button onclick="alert("success")">点我</button>
这时,我在success外使用了HTML语法字符""(双引号),这时不会弹出窗口,而是报错语法错误。但是我如果还是希望使用双引号呢? 这时就要用"实体来代替HTML中的语法字符。如下例所示:
例3:
<button onclick="alert("success")">点我</button>
<!-- 正常弹出窗口-->
这个例子中我们在JavaScript代码中使用了HTML实体而没有使用HTML语法字符,这时就不会报错了。
例4:
1
2
3
4
5
6
7
8
|
< button onclick="show()">点我</ button > <!-- 正常弹出窗口--> < script > function show(){ alert("success"); } </ script > |
这个例子中我们调用函数,而把函数定义放在了script中,这样也是可以的。因为:事件处理程序中的代码在执行时,有权访问到全局作用域中的任何代码。这句话怎么理解呢? 实际上,我们可以在chrome中观察button标签的作用域链。如下所示:
接下来我们再看看script所在的作用域,如下图所示:
可以看到script标签就在全局作用域。
也就是说目前button中的HTML事件处理函数在作用域链的最前端,而Script在全局作用域,所以“事件处理程序中的代码在执行时,有权访问到全局作用域中的任何代码。”这句话就不难理解了。
例5:
<button onclick="alert(event.type)">点我</button>
这时浏览器弹出窗口显示:click。这个例子是什么意思呢?注意到我并没有在event.type外加单引号,说明这并不是字符串。实际上,event是局部对象--在触发DOM上的某个事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息。而这里是弹出了对象了类型,即为click。
HTML事件处理程序的三个缺点(重点):
1. 时差问题。 因为用户可能在HTML元素一出现就开始触发相应事件,但是有可能该事件的脚本(如例4中show()函数的函数定义在script中)还没有加载完成,此时不具备执行条件,故报错。
解决方法:将HTML事件处理程序封装在一个try-catch块中,以便错误不会浮出水面。
<input type="button" value="click me" onclick="try{show();}catch(ex){}">
2.这样扩展事件实例程序的作用域链在不同的浏览器中会导致不同的结果(例4中我是在chrome中查看的作用域链,其他浏览器不一定是这样的,请注意)。不同JavaScript引擎遵循的标识符解析规则略有差异,很有可能会在访问非限定对象成员时出错。
3.HTML和JavaScript代码紧密耦合。 结果是:如果要更换事件处理程序,就必须改动两个地方--HTML代码和JavaScript代码。
那么怎么解决上面的问题呢? DOM0级事件处理程序是一个不错的选择!
第二部分:DOM0级事件处理程序
DOM0级事件处理程序用的也非常普遍。之所以成为DOM0级,我认为是当时还没有出DOM标准,而IE和Netscape Navigator两者使用的时间处理程序(不知是否合理,望批评指正)。 总之,我们先看看下面的例子吧。
例6:
<button id="button">点我</button>
<script>
var button=document.getElementById("button");
button.onclick=function(){
alert("clicked");
}
</script>
即我们先在script中取得元素的引用,然后再将一个函数赋值给onclick事件处理程序。 之前介绍过,事件处理程序即为函数,而button.onclick这种形式即函数作为了对象的方法。那么对象的方法即事件处理程序是在元素(对象)的作用域中运行而非在全局作用域中运行的,因为方法是属于对象的。(注意:例4中事件处理程序是在全局作用域中运行的)。 如果这个函数中存在this关键字,那么this就会指向这个对象。下面我们在浏览器中证明事件处理程序是在元素的作用域中运行。
我们看到alert("clicked");确实是在button中运行的。
我们还可以通过下面的方式删除通过DOM0级方法指定的事件处理程序。
button.onclick=null;
通过上面的分析我们可以知道DOM0级事件处理程序是非常不错的,它解决了HTML事件处理程序的三个缺点:时差问题、作用域链导致的不同浏览器表现不一致问题和HTML和JavaScript紧密耦合问题。
但是,DOM0级事件处理程序并不是完美的,它同样有两个缺点:
- 我们不能给一个元素同时添加两个事件。
- 我们不能控制元素的事件流(捕获or冒泡)。
对于第二个问题后面会讲到,第一个问题举例如下:
<button id="button">点我</button>
<script>
var button=document.getElementById("button");
button.onclick=function(){
alert("clicked");
}
button.onclick=function(){
alert("again");
}
虽然我对同一个元素设置了两个事件处理程序,但是最终的结果是:只有第二个事件有效(覆盖了第一个事件)。当然,人类是聪明的动物,DOM2级事件很好的解决了这个问题!
第三部分:DOM2级事件处理程序
DOM2级事件处理程序定义了两个方法:
- addEventListener() ---添加事件侦听器
- removeEventListener() ---删除事件侦听器
在博文的开头我就提到了事件处理程序即事件侦听器。这两个方法都接收三个参数:
- 要处理的事件名(注意:是时间名,所以没有on!),如click、mouseover等。
- 作为事件处理程序的函数,如function(){alert("clicked");}
- 表示事件流方式的布尔值。false为冒泡阶段调用事件处理程序;true为捕获阶段调用事件处理程序。对于冒泡和捕获这两种时间流可以看《JavaScript中的两种事件流》
下面通过两个例子加深理解:
例7:
<button id="button">点我</button>
<script>
var button=document.getElementById("button");
button.addEventListener("click",function(){
alert(this.id);
},false);
button.addEventListener("click",function(){
alert("another event");
},false);
</script>
结果:第一次弹出窗口:button。
第二次弹出窗口:another event。
结论:通过DOM2级事件处理程序,我们可以为同一个元素添加两个或更多的事件。事件根据顺序依次触发。且this同样指向当前元素,故函数在元素的作用域中执行。
this分析:和前面的DOM0级事件处理程序一样,这里的addEventListener同样也可以看作对象的方法,不同之初在于,DOM0级的方法需要另外一个函数来赋值,而这里的方法是DOM2级规范预定义的。
removeEventListener()这个删除事件处理程序的方法值得注意的是:使用addEventListener()来添加的事件处理程序只能通过它来移除,且需要传入相同的参数。
例8:
<button id="button">点我</button>
<script>
var button=document.getElementById("button");
button.addEventListener("click",function(){
alert(this.id);
},false);
button.removeEventListener("click",function(){
alert("another event");
},false);
上述代码貌似可以移除click的事件处理程序,但是通过实验证明是不可以的,原因是:事件处理程序为匿名函数时无法移除。看下面的成功移除的例子:
例9:
<button id="button">点我</button>
<script>
var button=document.getElementById("button");
function handler(){
alert(this.id);
}
button.addEventListener("click",handler,false);
button.removeEventListener("click",handler,false);
</script>
成功移除!
注意:1.传入方法的handler没有(),是因为这里都只是定义函数,而不是调用,需要注意。
2.这两个方法的第三个参数都是false,即事件处理程序添加到冒泡阶段。一般不使用true,因为低版本的IE不支持捕获阶段。
DOM2级事件处理程序成功地解决了前面所有事件处理程序的问题,堪称perfect!!!! 然而总是特立独行的IE浏览器又有新花样,它也有自己的一套事件处理程序,下面我们就来看看吧。
第四部分:IE事件处理程序
IE事件处理程序中有类似与DOM2级事件处理程序的两个方法:
- attachEvent()
- detachEvent()
它们都接收两个参数:
- 事件处理程序名称。如onclick、onmouseover,注意:这里不是事件,而是事件处理程序的名称,所以有on。
- 事件处理程序函数。如function(){alert("clicked");}
之所以没有和DOM2级事件处理程序中类似的第三个参数,是因为IE8及更早版本只支持冒泡事件流。
注意:
1.IE事件处理程序中attachEvent()的事件处理程序的作用域和DOM0与DOM2不同,她的作用域是在全局作用域中。因此,不同于DOM0和DOM2中this指向元素,IE中的this指向window。
2.同样,我们可以使用attachEvent()来给同一个元素添加多个事件处理程序。但是与DOM2不同,事件触发的顺序不是添加的顺序而是添加顺序的相反顺序。
3.同样地,通过attachEvent()添加的事件处理程序必须通过detachEvent()方法移除,同样的,不能使用匿名函数。
4.支持IE事件处理程序的浏览器不只有IE浏览器,还有Opera浏览器。
第五部分:跨浏览器的事件处理程序
实际上,这一部分视为了跨浏览器使用,将前面的几部分结合起来就可以了。
这一部分需要创建两个方法:
- addHandler() --这个方法职责是视情况来使用DOM0级、DOM2级、IE事件处理程序来添加事件。
- removeHandler()--这个方法就是移除使用addHandler添加的事件。
这两个方法接收相同的三个参数:
- 要操作的元素--通过dom方法获取
- 事件名称--注意:没有on,如click、mouseover
- 事件处理程序函数--即handler函数
这两个方法的构造情况如下:
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
}else if(element.attachEvent){
element.attachEvent("on"+type,handler);
}else{
element["on"+type]=handler;
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
}else if(element.detachEvent){
element.detachEvent("on"+type,handler);
}else{
element["on"+type]=null;
}
}
};
即先判断DOM2级事件处理程序,再判断IE事件处理程序,最后使用DOM0级事件处理程序。
例10:通过这个例子来使用上面构造的方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跨浏览器事件处理程序</title>
</head>
<body>
<button id="button">点我</button>
<script>
var EventUtil={
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
}else if(element.attachEvent){
element.attachEvent("on"+type,handler);
}else{
element["on"+type]=handler;
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);//注意:这里默认使用了false(冒泡)
}else if(element.detachEvent){
element.detachEvent("on"+type,handler);
}else{
element["on"+type]=null;
}
}
};
function handler(){
alert("clicked");
}
var button=document.getElementById("button");
EventUtil.addHandler(button,"click",handler);
</script>
</body>
</html>
最后浏览器成功弹出“clicked”。
4.使用单页应用将文件上传到服务器的有哪些方法?
提示:XMLHttpRequest2(streaming),fetch(non-streaming),File API
一、XMLHttpRequest2(streaming)
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,需要首先补充这方面知识。
基本的Fetch请求
让我们首先来比较一个XMLHttpRequest使用示例与fetch方法的使用示例。该示例向服务器端发出请求,得到响应并使用JSON将其解析。
XMLHttpRequest
一个XMLHttpRequest需要设置两个事件回调函数,一个用于获取数据成功时调用,另一个用于获取数据失败时调用,以及一个open()方法调用及一个send()方法调用。
function reqListener(){ var data=JSON.parse(this.responseText); console.log(data); } function reqError(err){ console.log("Fetch错误:"+err); } var oReq=new XMLHttpRequest(); oReq.οnlοad=reqListener; oReq.οnerrοr=reqError; oReq.open("get","/students.json",true); oReq.send();
Fetch
一个fetch()方法的使用代码示例如下所示:
fetch("/students.json") .then( function(response){ if(response.status!==200){ console.log("存在一个问题,状态码为:"+response.status); return; } //检查响应文本 response.json().then(function(data){ console.log(data); }); } ) .catch(function(err){ console.log("Fetch错误:"+err); });
在上面这个示例中,我们在使用JSON解析响应前首先检查响应状态码是否为200。
一个fetch()请求的响应为一个Stream对象,这表示当我们调用json()方法,将返回一个Promise对象,因为流的读取将为一个异步过程。
响应元数据
在上一个示例中我们检查了Response对象的状态码,同时展示了如何使用JSON解析响应数据。我们可能想要访问响应头等元数据,代码如下所示:
fetch("/students.json") .then( function(response){ console.log(response.headers.get('Content-Type')); console.log(response.headers.get('Date')); console.log(response.status); console.log(response.statusText); console.log(response.type); console.log(response.url); } )
响应类型
当我们发出一个fetch请求时,响应类型将会为以下几种之一:“basic”、“cors”或“opaque”。这些类型标识资源来源,提示你应该怎样对待响应流。
当请求的资源在相同域中时,响应类型为“basic”,不严格限定你如何处理这些资源。
如果请求的资源在其他域中,将返回一个CORS响应头。响应类型为“cors”。“cors”响应限定了你只能在响应头中看见“Cache-Control”、“Content-Language”、“Content-Type”、“Expires”、“Last-Modified”以及“Progma”。
一个“opaque”响应针对的是访问的资源位于不同域中,但没有返回CORS响应头的场合。如果响应类型为“opaque”,我们将不能查看数据,也不能查看响应状态,也就是说我们不能检查请求成功与否。目前为止不能在页面脚本中请求其他域中的资源。
你可以为fetch请求定义一个模式以确保请求有效。可以定义的模式如下所示:
- "same-origin":只在请求同域中资源时成功,其他请求将被拒绝。
- "cors":允许请求同域及返回CORS响应头的域中的资源。
- "cors-with-forced-preflight":在发出实际请求前执行preflight检查。
- "no-cors"针对的是向其他不返回CORS响应头的域中的资源发出的请求(响应类型为“opaque”),但如前所述,目前在页面脚本代码中不起作用。
为了定义模式,在fetch方法的第二个参数中添加选项对象并在该对象中定义模式:
fetch("http://www.html5online.com.cn/cors-enabled/students.json",{mode:"cors"}) .then( function(response){ console.log(response.headers.get('Content-Type')); console.log(response.headers.get('Date')); console.log(response.status); console.log(response.statusText); console.log(response.type); console.log(response.url); } ) .catch(function(err){ console.log("Fetch错误:"+err); });
Promise方法链
Promise API的一个重大特性是可以链接方法。对于fetch来说,这允许你共享fetch请求逻辑。
如果使用JSON API,你需要检查状态并且使用JSON对每个响应进行解析。你可以通过在不同的返回Promise对象的函数中定义状态及使用JSON进行解析来简化代码,你将只需要关注于处理数据及错误:
function status(response){ if(response.status>=200 && response.status<300){ return Promise.resolve(response); } else{ return Promise.reject(new Error(response.statusText)); } } function json(response){ return response.json(); } fetch("/students.json") .then(status) .then(json) .then(function(data){ console.log("请求成功,JSON解析后的响应数据为:",data); }) .catch(function(err){ console.log("Fetch错误:"+err); });
在上述代码中,我们定义了status函数,该函数检查响应的状态码并返回Promise.resolve()方法或Promise.reject()方法的返回结果(分别为具有肯定结果的Promise及具有否定结果的Promise)。这是fetch()方法链中的第一个方法。如果返回肯定结果,我们调用json()函数,该函数返回来自于response.json()方法的Promise对象。在此之后我们得到了一个被解析过的JSON对象,如果解析失败Promise将返回否定结果,导致catch段代码被执行。
这样书写的好处在于你可以共享fetch请求的逻辑,代码容易阅读、维护及测试。
POST请求
在Web应用程序中经常需要使用POST方法提交页面中的一些数据。
为了执行POST提交,我们可以将method属性值设置为post,并且在body属性值中设置需要提交的数据。
fetch(url,{ method:"post", headers:{ "Content-type":"application:/x-www-form-urlencoded:charset=UTF-8" }, body:"name=lulingniu&age=40" }) .then(status) .then(json) .then(function(data){ console.log("请求成功,JSON解析后的响应数据为:",data); }) .catch(function(err){ console.log("Fetch错误:"+err); });
使用Fetch请求发送凭证
你可能想要使用Fetch发送带有诸如cookie之类的凭证的请求。你可以在选项对象中将credentials属性值设置为“include”:
fetch(url,{ credentials:"include" })
三、File API
file对象是对文件对象的一种表现
代表input上传时的文件独享对象
IE9中没有这个对象,所以无法操作文件
5.CSS 重排和重绘之间有什么区别?
哪些 CSS 属性会导致重排及重绘?
重绘是一个元素的外观变化所引发的浏览器行为;
重排是引起DOM树重新计算的行为;
1、回流/重排
渲染树的一部分必须要更新且节点的尺寸发生了变化,会触发重排操作。每个页面至少在初始化的时候会有一次重排操作。
2、重绘
部分节点需要更新,但没有改变其形状,会触发重绘操作。
会触发重绘或回流/重排的操作
1、添加、删除元素(回流+重绘)
2、隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
3、移动元素,如改变top、left或移动元素到另外1个父元素中(重绘+回流)
4、改变浏览器大小(回流+重绘)
5、改变浏览器的字体大小(回流+重绘)
6、改变元素的padding、border、margin(回流+重绘)
7、改变浏览器的字体颜色(只重绘,不回流)
8、改变元素的背景颜色(只重绘,不回流)
6. 什么是 CSS 选择器权重以及它如何工作?
说说计算 CSS 选择器权重的算法。
4个等级的定义如下:
第一等:代表内联样式,如: style=””,权值为1000。
第二等:代表ID选择器,如:#content,权值为100。
第三等:代表类,伪类和属性选择器,如.content,权值为10。
第四等:代表类型选择器和伪元素选择器,如div p,权值为1。
例如上图为例,其中#NAV为二等选择器,.ACTIVE为三等选择器,UL、LI和A为四等选择器。则整个选择器表达式的特殊性的值为1*100+1*10+3*1=113
注意:通用选择器(*),子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以他们的权值都为0。
7.CSS 中的 pixel 与硬件/物理中的 pixel 有何不同?
提示:像素不是像素不是像素 – ppk。
而css pixel * devicePixelRatio = 实际屏幕像素。
(平时电脑屏幕的devicePixelRatio都是1所以感觉不到,会让你误以为css里的px就是实际屏幕像素)
同时media query使用的px显然也是css pixel。
8.什么是 sectioning 算法?
提示:它也被称为 HTML5 大纲算法。特别是在构建具有语义结构的网站时非常重要。
在html5中有一个很重要的概念,叫做html5大纲算法(HTML5 Outliner),它的用途为用户提供一份页面的信息结构目录。合理的使用HTML5元素标签,可以生成一个非常清晰的文档大纲。
HTML5大纲算法
我们可以通过各种工具去查看当前页面,这里推荐使用一个测试工具:HTML5 Outliner,网址如下:https://gsnedders.html5.org/outliner/
1. 了解一个 section 和 div 的区别
①div元素在html5之前是最常用的最流行的标签,但他本身是没有任何语义的,它只不过是用来布局页面和css样式以及js样式。
②在html5中 section 标签并不是用来取代 div 的。他是具有语义的文档标签,在大纲规范中规定 session 至少要包含一个标题。也就是 section 标签内至少包含一个h1~h6。
③如果是页面布局,且不是 header、footer之类的专属区域都应该使用div。
2. body\nav\section 都是需要有标题才规范,header和div则是不需要标题的。
3. section 和 nav 元素大纲要求有标题h1~h6,暗示 section 必须有才规范,而 nav 如果没有标题,也是合理的。给他添加了标题会让大纲更好看,所以我们可以添加完了再隐藏,就不会破坏布局了。(通过display:none;将其隐藏)
9.如果你用过 CSS Flex / CSS Grid(网格)布局,请说明你为什么要使用它?它为你解决了什么问题?
- 使用 CSS Grid,百分比%和 fr 单位有何不同?
- fr是一个相对尺寸单位,表示剩余空间做等分,此项分配到的百分比(如果只有一个项使用此单位,那就占剩余空间的100%,所以多个项联合使用更有意义)
- 使用 CSS flexbox,有时 flex-items/children 会不考虑 flex 容器设置的宽度/高度?为什么会这样?
- 可以使用 CSS Grid 创建 Masonry layout(瀑布流布局)吗?如果可以,怎么做?
- 解释 CSS Grid 和 CSS flexbox 术语?
- CSS Grid布局 (又名"网格"),是一个基于二维网格布局的系统,主要目的是改变我们基于网格设计的用户接口方式
- 浮动元素(
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("浏览器不支持触屏");
}
};
- 你是否不看好检测设备对触摸事件的支持?如果是,为什么?
- 主流做法一般是通过 UserAgent 判断。
- 比较触摸事件和点击事件。
-
在触摸设备上,touch事件从手指刚碰到屏幕就会触发,而click事件则要晚一会儿才被触发。触发顺序如下:
touchstart
mouseover
mousemove(一次)
mousedown
mouseup
click
touchend
所以,如果想提升web在触摸设备下的用户体验,让用户觉得响应非常迅速,应该对页面的触摸事件进行事件处理程序的注册,而不应再关注click事件。 - 当设备同时支持触摸和鼠标事件时,你认为这些事件的正确事件顺序是什么或应该是什么?
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引用放在</body>标签之前。
script标签存在两个属性,defer和async,因此script标签的使用分为三种情况:
1.<script src="example.js"></script>
没有defer或async属性,浏览器会立即加载并执行相应的脚本。也就是说在渲染script标签之后的文档之前,不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载;
2.<script async src="example.js"></script>
有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;
3.<script defer src="example.js"></script>
有了defer属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是并行进行的(异步),js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。
下图可以直观的看出三者之间的区别:
其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。
从图中我们可以明确一下几点:
1.defer和async在网络加载过程是一致的,都是异步执行的;
2.两者的区别在于脚本加载完成之后何时执行,可以看出defer更符合大多数场景对应用脚本加载和执行的要求;
3.如果存在多个有defer属性的脚本,那么它们是按照加载顺序执行脚本的;而对于async,它的加载和执行是紧紧挨着的,无论声明顺序如何,只要加载完成就立刻执行,它对于应用脚本用处不大,因为它完全不考虑依赖。