整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

js基础面试题21-30道

1.for in、Object.keys 和 Object.getOwnPropertyNames 对属性遍历有什么区别?

参考答案:

  • for in 会遍历自身及原型链上的可枚举属性
  • Object.keys 会将对象自身的可枚举属性的 key 输出
  • Object.getOwnPropertyNames会将自身所有的属性的 key 输出

解析:

ECMAScript 将对象的属性分为两种:数据属性和访问器属性。

var parent = Object.create(Object.prototype, {
    a: {
        value: 123,
        writable: true,
        enumerable: true,
        configurable: true
    }
});
// parent继承自Object.prototype,有一个可枚举的属性a(enumerable:true)。

var child = Object.create(parent, {
    b: {
        value: 2,
        writable: true,
        enumerable: true,
        configurable: true
    },
    c: {
        value: 3,
        writable: true,
        enumerable: false,
        configurable: true
    }
});
//child 继承自 parent ,b可枚举,c不可枚举

for in

for (var key in child) {
    console.log(key);
}
// b
// a
// for in 会遍历自身及原型链上的可枚举属性

如果只想输出自身的可枚举属性,可使用 hasOwnProperty 进行判断(数组与对象都可以,此处用数组做例子)

let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
    if (arr.hasOwnProperty(i)) {
        console.log(arr[i]);
    }
}
// 1
// 2
// 3

Object.keys

console.log(Object.keys(child));
// ["b"]
// Object.keys 会将对象自身的可枚举属性的key输出

Object.getOwnPropertyNames

console.log(Object.getOwnPropertyNames(child));
// ["b","c"]
// 会将自身所有的属性的key输出

参与互动

文最初发布于 Dropbox 技术博客,经 Dropbox 授权由 InfoQ 中文站翻译并分享。译文经过了 Dropbox 团队的审核和修订。

序言

2017 年 5 月,我首度加入 Dropbox 的时候,从 CoffeeScript 向 TypeScript 迁移的工作已经接近尾声。彼时,需要对已有的 CoffeeScript 文件更改时,一般都会先将它转换为 TypeScript。我们的部分代码库仍在使用 react-dom-factories,并且在 Redux 之前有一个自定义的 flux 实现。

那时我们的 Web 平台团队正全速向 TypeScript 迁移,但这一工作的规模或复杂性尚不为外人所知。如今 TypeScript 已成为 JavaScript 事实上的超集,我们的这段往事也是时候公之于众了。故事主要发生在 2017 年,在今天依旧颇具参考价值。

我联络到该项目的首席工程师之一 David Goldstein 来撰写本文。此外,还找到了另一位见证者,Web 平台工程师 Samer Masterson 来补充细节。

将数十万行 CoffeeScript 代码迁移到 TypeScript 是一项庞大的工程,本文将涉及其中的方方面面。我们将介绍一开始为什么选择了 TypeScript,如何规划迁移工作,还有那些计划外的各种细节。

迁移在 2017 年秋季结束。在此过程中我们开发了一些优秀的工具,并成为了首批大规模采用 TypeScript 的公司之一。——Matthew Gerstman

历史:采用 CoffeeScript

早在 2012 年,我们还是一家只有约 150 名员工的新兴公司。当时浏览器中的最新技术是 jQuery 和 ES5。HTML5 还有两年才会正式登台,而 ES6 还要等三年。由于 JavaScript 技术似乎停滞不前,我们想要找到一种更先进的 Web 开发方法。

当时,CoffeeScript 非常流行。它支持箭头函数,智能 this 绑定,甚至可选链,都比标准 JavaScript 领先数年。最后,我们的两名工程师在 2012 年的“黑客周”中将整个 dropbox.com Web 应用程序从 JavaScript 迁移到了 CoffeeScript 上。彼时 dropbox 规模不大,所以迁移很容易。我们从 CoffeeScript 社区获得了指导,并采纳了他们的样式建议,最终将 coffeelint 集成到了工作流程中。

在 CoffeeScript 中,花括号、圆括号,有时甚至逗号都是非必须的,是可有可无的选项。

例如,foo 12 与 foo(12) 是等同的。

多行数组可以不用逗号:

复制代码

// CoffeeScript["foo""bar"]  // JavaScript["foo", "bar"]

这种语法方法在那时很流行,我们甚至采纳了社区的“可选的符号就不用写”建议。

当时,代码库包含约 100,000 行 JavaScript。所有文件按预先指定的顺序串联在一起打包发布。尽管公司的许多工程师都可以看到这些代码,但其中全职的 Web 工程师却不到 10 位。

自然,这种方法无法很好地扩展;在 2013 年我们采用了 RequireJS 模块系统,并开始编写新代码以符合“异步模块定义”(简称 AMD)规范。我们确实考虑过 CommonJS,但那时 npm 和 Node 生态系统尚未成熟,因此我们选择了专为在浏览器中使用而设计的工具。如果是几年后再做同样的决策,我们可能会改用 CommonJS。

语言迁移的号角声

一开始还好,但到了 2015 年底,产品工程师开始对 CoffeeScript 愈加不满。ES6 于当年早些时候发布,覆盖了 CoffeeScript 的那些最佳特性,与 CoffeeScript 相比,它具备更多优势。它支持对象和数组解构、类语法和箭头函数。结果一些团队抢先一步,开始在自己的独立项目中使用 ES6。

与此同时,CoffeeScript 代码库维护难度在加大。由于 CoffeeScript(和标准 JavaScript)都没有类型,因此很容易在无意间破坏某些内容。防御式编程随处可见,但却使代码难以理解。我们为 null 和 undefined 添加了额外的保护措施,还针对某种极端场景采用了特殊对策,无需 new 操作便可以安全构造一个函数。

复制代码

class URI    constructor: (x) ->      # enable URI as a global function that returns a new URI instance      unless @ instanceof URI        return new URI(x)      ...

此外,CoffeeScript 是一种基于空格的语言,即 tab 和空格具备不同的含义的,这与用 Python 构建 Dropbox 类似。然而,CoffeeScript 对标点却过于宽容。通常,“可选的标点”实际上意味着“CoffeeScript 会将其编译为意想不到的含义。”

举个例子:在 2013 年的秋天,曾经遇到过一个关于标点符号的 bug,Python 无法编译通过,CoffeeScript 将它进行了错误的编译。虽然 Coffee Script 与 Python 的相似性可能有助于 Dropbox 的应用,但这些差异往往会出问题。一些更有经验的开发人员选择通过将 JavaScript 与 CoffeeScript 代码并排打开来工作。

2015 年 11 月,对 Dropbox 的前端工程师进行了一项调查,发现只有 15%的受访者认为应该继续使用 CoffeeScript,而 62%的受访者则认为应该放弃它:

开发人员经常抱怨:

  • 缺少分隔符
  • 过于固执己见的句法糖
  • 缺乏社区对语言的支持
  • 由于语法密集而难以理解
  • 由于句法歧义而容易出错

基于开发人员的这些反馈,于是我们将目光转向业界,决定试用 TypeScript 和标准 ES6。我们将它们都集成到了 dropbox.com 技术栈中以选出更适合的选项。我们也考虑过 Flow,但它不如 TypeScript 流行,相关支持也较少。最后我们决定,如果要用类型语言就用 TypeScript,这在 2015 年是不寻常的决策。

2016 年上半年,有一位工程师将 Babel 和 Type 脚本集成到我们的构建脚本中。我们现在可以在主网站试用两种语言。经过生产测试,我们认为 TypeScript 实际上是带有类型的 ES6。由于团队偏爱类型,最终选择了 TypeScript。

但是有一个小问题:那时我们的代码库已增长到 329,000 行 CoffeeScript;我们的工程团队也大幅扩张,不再由单个团队负责整个网站。所以我们的迁移速度不会像上次那么快了。

乐观的迁移计划

最初的计划有 5 大里程碑:

M1:基本支持

  • 添加 TypeScript 编译器。
  • 使 TypeScript 和 CoffeeScript 代码可以互操作。
  • TypeScript 的基本测试、国际化和 linting。

M2:选定 TypeScript 为新代码的默认语言

  • 优化开发人员体验。
  • 迁移核心库。
  • 为最佳实践编写文档。
  • 为代码迁移编写文档。

M3:TypeScript 成为代码库的主成员

  • 在 M2 基础上更进一步,通过更多的教育过程,完整 linting 和测试支持,将其余重要的库进行转换。

M4:预期在 2017 年 4 月,将编辑最多的一组文件迁移到 TypeScript

  • 手动将约 100 个经常编辑的文件从 Coffeescript 转换为 TypeScript。原始的 CoffeeScript 将在 git 历史中可用。

M5:预期在 2017 年 7 月,删除 CoffeeScript 编译器

  • 将所有剩余 CoffeeScript 代码转换成 JavaScript。源 CoffeeScript 将在 git 历史中可用。
  • 更改这些 JavaScript 代码前需将整个文件迁移到 TypeScript。

2016 年下半年,M1、M2 和 M3 顺利完成。我们成功构建了稳健的 Coffee/TypeScript 互操作程序。测试很简单:重用现有的基于 Jasmine 的基础架构来测试两种语言(之后迁移到了 Jest,但这是另一个故事了)。我们整合了 TSLint 并编写了样式指南。

M4 和 M5 遇到了不少障碍,因为产品团队需要将已有代码移植到 TypeScript 上。我们希望各个团队负责迁移各自开发的代码,并决定给产品团队留出一年中 20%的时间用于“基础工作”,后文会详细说明。

CoffeeScript/TypeScript 的互操作性

我们实现了 CoffeeScript 和 TypeScript 的互操作,如下所示:对于每个 CoffeeScript 文件,在类型文件夹中创建了一个相应的.d.ts 声明文件。这些都是自动创建的,如:

复制代码

declare module "foo/bar" {  const exports: any;  export = exports;}

也就是说所有内容都变成了 any 类型。重要模块可以转换为 TypeScript,或者逐步改变类型。对于流行的外部库(如 jQuery 或 React),可以从 DefinitelyTyped 找出可用的类型。对于不太常见的库,采用与默认存根相同的方法。

将所有 TypeScript 和 CoffeeScript 文件放在同一文件夹中,所以两种语言的文件模块 ID 都一样。在学习 AMD import/export 与 TypeScript 的语法如何对应时我们遇到了些麻烦,还好问题不大。我们没有使用 --esModuleInterop。

等效的 import 语句如下:

TypeScript(推荐)

复制代码

import * as foo from "foo";

TypeScript(不推荐)

复制代码

import foo = require("foo");

与 AMD JavaScript(或等效的 CoffeeScript)相同

复制代码

define(["foo", ...], function(foo, ...) { ... }

将导出命名为 export const foo 类;可以导入模块然后解构{foo},实现在 CoffeeScript 中读取。这样就和标准的 ES6 命名 import 建立了良好的语法关系。TypeScript 的 export default 导入到 AMD 模块后,等效于对象{default: …},真是令人惊讶。

大多数模块都可以用这些等效方法,但有些模块会动态确定它们将导出的内容。我们从每个文件导出了所有可能的导出,如果没有返回的话就改为 undefined。

之前

复制代码

define([...], function(...) {  ...  if (foo) {    return {bar};  } else {    return {baz};  }})

之后

复制代码

let foo, bar;if (foo) {  bar = // define bar;} else {  baz = // define baz;}// Export both regardless.export {bar, baz}

禁用 CoffeeScript 新文件

M2 阶段代码库不再接收新的 CoffeeScript 文件。已有 CoffeeScript 的编辑不受影响,但多数工程师也因此开始学习 TypeScript 了。

一开始我们编写了一个遍历代码库的测试,找到所有.coffee 文件并将其路径加入白名单。对此测试文件的任何更改都需要经过一位 Web 平台工程师的审核。

同时我们采用了 Bazel 作为构建系统。在迁移到 Bazel 期间这一测试暂时失效了,为已有的 CoffeeScript 文件返回了一个空列表,还断言该空列表是已有 CoffeeScript 文件白名单的子集。还好我们很快修复了这个问题,没有造成严重影响。

我们在这里学到了一个教训:如果测试中带有任何假设,请试着确保它们能够测试这些假设并在中断时报错。原始测试应该断言 CoffeeScript 文件列表为非空,这样一旦出错时,就能立刻发现问题。

修复这个问题时,我们对白名单加入了严格的检查,这样文件删除时也必须从白名单中移除,且不能重新引入(除非明确地重新添加文件)。这种方法之后用在了所有白名单相关工作上,既能让不符合测试假设的问题快速暴露,又能避免人们无意间回退迁移工作。这里有一个小的缺陷:缩小白名单会阻断代码审核,但问题不大,我们会尽快(在一个工作日内)接受这些审核。

早期经验:没有遗漏 CoffeeScript 的语法糖

最初选择要迁移的语言时,我们担心的一个问题是:ES6 和 TypeScript 并没有包括 CoffeeScript 的所有特性,比如说没有? 和?. 运算符。

起初,我们以为会遗漏这些:但当采用了 TypeScript2.0 的 --strictNullChecks 后,这就不是问题了。可选链运算符主要用来处理 undefined 或 null 之类的不确定性,而 TypeScript 帮助我们消除了这种不确定性。

有趣的是,optional chaining 和 nulllish coallescing 最近都被重新添加到 vanilla Java 脚本中,并以类型脚本语言显示,尽管有一些小的语法变化与原始 CoffeeScript 变量之间略有差异。

优先级竞争

2016 年下半年,公司成立了一个并行团队,用 React 重新设计和重构我们的网站。他们的目标是:到 2017 年第一季度末(时间接近最初的 M4 里程碑)发布新网站。该项目称为“Maestro”,优先级比将他们负责的部分迁移到 TypeScript 的工作更高。此外其他一些团队也会参与其中。

经过讨价还价,Maestro 团队最终承诺在第二季度完成迁移工作。前面他们就用 React 和 TypeScript 重写了很多功能,剩下的文件则在第二季度迁移完毕。

迁移过程中用到“highly edited ”这个工具,强烈鼓励社区转换它们。可惜 100 个文件好像太多了,这个里程碑没有按时交付。

这样来看,删除 CoffeeScript 编译器的计划也得推迟了。除了这 100 个热门文件,后面还有 2000 多个虽然没那么常用,但也时不时用得上的 CoffeeScript 老文件呢。

推迟 M5

M5 里程碑在组织中引起了很多混乱,通常把它总结为“去除 CoffeeScript 编译器”。

公司内却出现了另一种解释。许多人认为,虽然无法在截止日期之后编写 CoffeeScript,但产品团队可以编辑本应该只读的代码,甚至可以编辑 CoffeeScript,然后检查新的编译后的代码。

可如果只 check in 已编译的代码,那么大部分代码就不会有 i18n 与 linting 支持了;不想追加投资的话,应假设代码没变才能找回这些支持。

此外,从平台的角度来看,这个里程碑意义不大。去除编译器主要是为了有一个单语言的代码库,并让注意力集中在 TypeScript 工具链上。

不知道“只读 JavaScript”是否比保留为 CoffeeScript 文件更好,用 Bazel 重新实现构建系统的工作即将完成,并已对 CoffeeScript 和 TypeScript 编译器都提供了支持。

因此在 6 月,TypeScript 的迁移工作被无限期推迟,完成时间没有 ETA。

事后看来这一决定似乎是不可避免的。假设每个工程日(包括测试和代码审查)大约要转换 1000 行代码,那么一位工程师要花一年的时间才能完成迁移。这个速度实际上是非常乐观的,因为实际报告的进度每天大约是 100 行,指望一两个月就完成根本做不到。

至于之前承诺的“20%的时间用于基础工作”,我们也没有达成共识。有的人知道这是用来满足基础架构需求的时间,有的人则认为这些时间可以用来偿还自己的技术债。而且 20% 这个限制也形同虚设,没人真的遵守它。

2017 年后,我们再做迁移时就不再开这种空头支票了。

使用 decaffeinate 的新方案

对 decaffeinate 的早期测试

早在 2017 年 1 月,一些工程师就曾使用 decaffeinate 来简化代码转换工作,甚至开始围绕它构建一些工具来处理 AMD,并通过一些开源代码来清理 React 样式。

不幸的是,我们首次尝试 decaffeinate 时出现了严重的故障。我们转换了 i18n 库,然后审查,测试并交付生产,结果发现 decaffeinate 误转换了未测试的,可识别语言环境的排序函数。只有一个页面用了这个函数,但在 Safari 中这个页面完全错乱了。之后我们查看了 decaffeinate 的错误积压,结果发现了几十个类似问题。我们也不知道需要花多久才能真正信任 decaffeinate,所以当时没打算用这种方法。

不过一些工程师还是决定使用它来手动转换代码,我们在文档中将其记为一种可行的工作流程。基于 decaffeinate 的脚本通常会生成明显无效的代码,这没什么大不了的,因为 TypeScript 在编译时会报告它们。真正的问题是潜在的 bug,它们改变了代码的语义,编译器却发现不了。

六个月后

2017 年夏天,decaffeinate 声明自己做到了无 bug。于是我们开始重新考虑这一选项,经过研究发现:

  • decaffeinate 的声明应该是可信的
  • 更令人信服的是,我们的内部开发人员报告说,使用基于 decaffeinate 的脚本比手动转换的结果更加可靠。

于是我们制定了新计划:将剩余的迁移工作自动化。

现在对于 decaffeinate 无法提供类型的情况,可以添加为 any,直到 TypeScript 满意为止。这种方法有以下优点:

  • 工程师(尤其是新员工)不必再学习阅读(或编辑)CoffeeScript
  • Web 平台无需再支持 CoffeeScript linting、国际化和编译器
  • codemod 或静态分析之类工具的改进只需应对一种语言

迁移结束后,团队可以按自己的进度修复代码中的类型;无需再维护指向未转换的 CoffeeScript 的声明文件。

此时,产品团队的空闲时间不多了,迁移得不到代码所属团队的大量支持。而且要完成目标就要尽量减少引入的错误,有超过 2000 个文件要迁移,但错误超过一打就可能让项目延迟或取消。这意味着我们必须在保持保持现有代码语义的同时进行转换。

两阶段计划

需要针对所有文件创建一个多步流水线方法来完成迁移。

首先,运行 decaffeinate 以生成有效的 ES6。该代码没有类型,甚至包括了 pre-JSX React。然后我们用一个自制的 ES6 到 TypeScript 转换器处理这段 ES6 代码。

全面 decaffeinate

decaffeinate 有一些选项可以生成更漂亮的代码,代价是降低代码的正确率。这些选项以 --loose 开头。最初包括以下选项:

  • –loose-for-expressions
  • –loose-for-includes
  • –loose-includes

这样就无需用 Array.from() 包装代码的大部分内容。但尝试并测试后,我们发现了很多足以让我们对这些选项失去信心的错误——它们很可能引入了回归。

而下面这些选项引发错误为数不多,因此最终使用了它们:

  • –prefer-const
  • –loose-default-params
  • –disable-babel 构造方法

decaffeinate 会留下有关潜在样式问题的注释,例如,

复制代码

/* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */

此后,我们使用了几个 codemod 来清理生成的代码。首先,使用 JavaScript-codemod 转换函数,例如 function() {}.bind(this) 转换为箭头函数:() => {}。接下来,对于导入了 React 的文件,使用 react-codemod 更新了旧的 React.createElement 调用,并将 React.createClass 的实例转换为 class MyComponent extends React.Component。

这一过程生成了可运行的 Javascript,但仍使用 AMD 模块格式。就算修复了这个问题,它也没有使用我们的设置进行类型检查。我们希望最终的 TypeScript 代码使用与其余代码相同的标志,尤其是 noImplicitAny 和 strictNullChecks。

我们必须编写自己的自定义转换才能进行类型检查。

构建一个 ES6 到 TypeScript 转换器

自制转换器有很多工作要做:通过迭代便能解决影响文件的所有问题,为此需要编写一种工具来自动处理以下问题。

为了开发这些工具,我们主要使用 https://astexplorer.net/ 来探索在构建原型转换时将要使用的抽象语法树。

将 AMD 转换为 ES6 模块格式

首先,需要将 AMD import 更新为 ES6 import。

下面的代码:

复制代码

define(['library1', 'library2'], function(lib1, lib2) {})

会变成:

复制代码

import * as lib1 from 'library1'; import * as lib2 from 'library2';

在 CoffeeScript 中,销毁 import 是一种常见的模式,与 named import 关系很近。因此我们将:

复制代码

define(['m1', 'm2'], function(M1, {somethingFromM2}) {  var tmp = M1(somethingFromM2);});

转换为:

复制代码

import * as M1 from 'm1';import {somethingFromM2} from 'm2'; var tmp = M1(somethingFromM2);

对导出进行转换。如下代码:

复制代码

define(function() {  return {hello: 1}}

变为:

复制代码

export {1 as hello}

当无法转换为 named export 时,便回退到使用 export = 。例如:

复制代码

define([], function() {  let Something;  return Something = (function() {    Something = class Something {    }    return Something;  })();});

变为:

复制代码

let Something; Something = (function() {   Something = class Something {   }   return Something; })(); export = Something;

对于未用到的导入,之后会再做清理,以避免某些模块会产生全局副作用。因此我们改为将其转换为 import “x”; 样式,并注释说这可能是没必要的。

类型签名

接下来,我们必须将每个函数参数和 var 声明注解为 any 类型。例如,function(hello) {} 变为 function(hello: any) {} 。

我们还需要为在类内部分配给 this 的每个属性添加一个类属性。例如:

复制代码

class Hello {  constructor() {    this.hi = 1;  }   someFunc() {    this.sup = 1;  }}

会转换为:

复制代码

class Hello {  hi: any;  sup: any;  ...

为 React 添加类型

另外,需要使用带有类型的 React.Component 对 React 类组件进行注解。这些更改消除了许多 TypeScript 错误。

为转换编写文档

因为不想丢失任何给定文件的版本控制历史,所以我们自动在每个文件的顶部添加了一条消息,说明如何查找原始 coffeescript 版本。

复制代码

//// NOTE This file was converted from a Coffeescript file.// The original content is available through git with the command:// git show 21e537318b56:metaserver/static/js/legacy_js/widgets/bubble.coffee//

修复类型错误

我们不想添加不必要的 any;但就算经过上述管道处理,仍然会遇到数千种类型错误。因此,转换管道中的最后一步是一个脚本,其运行类型检查,解析类型检查输出,然后根据每个错误代码尝试在关联的 AST 节点上插入适当的 any 用法。

一开始,我们在脚本里使用了 node-falafel,但发现用它时需要解析 TypeScript,所以我们 fork 了 falafel,进而使用 tslint-eslint-parser 来替代它;这样我们只需重写需要更改的代码即可。

保持专注

我们的目标不是要做出最优秀的转换工具,而是要转换代码库。首先,从小的内部功能入手来测试工具,用它们来捕获转换工具中的崩溃以及读取输出时发现的明显错误,当不再出现转换崩溃之后,便开始在随机的代码库子集中查看数据类型错误。这暴露出一些非常常见的问题,例如无效变量和复杂表达式中的类型错误,这些问题都不难解决:可以直接删除无效变量,尽管在默认状态下,保留它们的初始化器,以防表达式会产生其它副作用 - 将类似这样的复杂表达式封装成:(this as any).foo 。但是:这种方法变得越来越低效,所以后来我们开始改变策略。

当将整个代码库可靠地转换为 TypeScript 后,便开始在整个代码库上试运行,并对结果进行类型检查。我们将类型错误按代码分组 (例如。“TS7030”),并统计了发生的情况。这样就可以专心针对最常见的错误开发修复程序,避免浪费时间和精力了。

这是一个重大转折点。在此之前,我们一直在不停地编写修补程序,以修复我们决定手动测试的各个文件中不时出现的各种错误。即便这样,我们还是不能确定能否得到一个成熟的工具。通过对每个错误代码的出现情况进行分组和计数,我们能够了解到还有多少工作要做,并且能够集中精力处理发生了十几次以上的类型错误。

对于那些发生频次比较少或至少频次少到不足以需要费力去通过工具修复的类型错误,我们计划稍后再手动进行修复。有一个令人难忘的例子是,在我们更改策略之前我们发现的一个问题:ES6 类构造器在调用 super() 之前无法执行任何操作。在 CoffeeScript 类构造器中随时调用 super() 都是合法的,因此当将它们转换为 ES6 类时 TypeScript 会报错。下面这种 CoffeeScript 代码最容易出这种问题:

复制代码

class Foo extends Bar  constructor: (@bar, @baz) ->    super()

decaffeinate 后变成:

复制代码

class Foo extends Bar {  constructor(bar, baz) {    this.bar = bar;    this.baz = baz;    super(); // illegal: must come first  }}

在几乎每个这样的实例中,在作业之前调用 super() 都是有效的,但是需要几分钟读取超类构造器以对此进行检查。我们发现的 super() 函数的误调用只有一两次真正存在问题, 这种情况对于自动更新代码库过程中发生的错误来说,错误次数不算太多(大约有 20 多次),所以手工对它们修复的难度不是太大。将容易修复的代码单列出来,安全地进行重新排序,对于那些较为复杂的情况,需要人工反复检查,不值得花时间重写。

转换完成时,我们的类型错误率约为:每个转换的文件有 0.5–1 个类型检查错误,需要手动修复。

因工具提升了信心

在编写工具的后期阶段,我们更关注如何安全地部署转换后的代码。只对转换后的代码进行类型检查是不够的,特别是考虑到我们要自动添加很多 any。

因此,在代码通过管道之前和之后,都会对代码运行的所有单元测试。这样就可以找出更多的错误,主要是隐藏的 CoffeeScript 错误代码,转换为 ts 后就会报错。每当发现一个错误,都会在整个代码库中搜索类似的模式来修复它。这种办法不行的时候,我们会在转换工具中添加一个断言,让它们在遇到可疑代码时迅速失效。

谈谈一个有趣的错误

这个错误是意外覆盖了导出的函数。

CoffeeScript 与大多数语言的不同之处在于:它没有变量阴影的概念。例如在 Javascript 中,如果你运行:

复制代码

let myVar = "top-level";function testMyVar() {  let myVar = "shadowed";  console.log(myVar);} testMyVar();console.log(myVar);

它会打印出来:

复制代码

shadowedtop-level

尽管它们共享相同的名称,但在 testMyVar 中创建的 myVar 与顶级 myVar 是不同的。这在 CoffeeScript 中是不可能做到的。等效代码如下所示:

复制代码

myVar = "top-level"testMyVar ->  myVar = "shadowed"  console.log(myVar)    testMyVar()console.log(myVar)

打印出来:

复制代码

shadowedshadowed

在代码中找到一个实例,如下所示:

复制代码

define(() ->  sortedEntries = (...) ->    ...    sortedEntries = entries.sortBy(getSortKey, cmpSortKey)    ...   return {    sortedEntries  }

sortedEntries 被声明为一个函数,但其自身的函数主体被一个实体数组覆盖。第一次调用该模块后,对模块内部 sortedEntries 的任何调用都将失败;但由于 sortedEntries 函数导出的是副本,因此我们从未发现此问题。该代码翻译为:

复制代码

let sortedEntries = function() {  ...  sortedEntries = entries.sortBy(getSortKey, cmpSortKey)} export { sortedEntries };

由于 TypeScript 代码使用的是 ES6 模块而不是 AMD 模块,因此 sortedEntries 将作为引用而不是副本导出。这意味着当另一个模块导入 sortedEntries 并调用它时,sortedEntries 成为了一个数组,随后对其进行的任何调用均将无效。

遇到过一次这个错误后,我们在翻译代码中添加了一个 assert ,如果发现导出的函数被重新分配时就能解决问题。

降低从稀松模式转换为严格模式的风险

在构建这些工具的过程中,我们意识到从 AMD 转换为 ES6 模块的副作用是:将有史以来第一次为绝大多数代码启用严格模式。

乍听起来,这似乎很可怕;为此我们通读了 严格模式的 MDN 文档,并制作了可预期行为的更改清单,然后逐一浏览清单,并找出减轻它们影响的方法。

对于大多数更改,我们发现 TypeScript 解析器或类型检查器就能处理了 -——TypeScript 会正常抱怨新的语法错误。有些更改则可以通过我们的代码搜索工具轻松验证。还有些更改则不是问题,因为 CoffeeScript 实际上在其代码生成中并未使用有问题的结构。

关于 eval、.caller 和.callee 的更改:我们在代码库中很少使用 eval,在 CoffeeScript 中都没有使用。并且我们没有使用.caller 和.callee,因此不必担心它们。

剩下的最后一类:只能通过运行代码来验证的更改。其中,与 eval 有关的更改是无关紧要的,而 arguments 很少用,很容易处理。这下需要担心的行为更改只剩下 3 种:

1、给不可写属性、getter-only 属性以及非扩展对象的属性的分配时会报错。向由 Object.freeze 冻结的对象写入属性是我们最有可能遇到的形式。

2、删除不可删除的属性现在会报错。

3、对 this 行为的更改——不再有 boxing,也不再有隐式 this=window 行为。

我们实际上无法提前知道这三个更改是否会带来问题,但现在这份简短的清单使我们更容易管理风险了。

还值得一提的是,代码库中最古老的部分是在引入 AMD 和 RequireJS 之前就以非模块化代码编写的内容,其中我们最担心的是非严格模式的行为可能是代码正常运行所必需的。

我们发现可以将代码转换为 TypeScript,而无需将其转换为 ES6 模块。这样一来便可以保持稀松模式。虽然这意味着我们在这部分代码中基本上没有跨模块的类型检查,但我们认为这是可以接受的折衷方案。

第一次转换后的特征

我们首先对 Jasmine 测试套件开始了大规模转换(后来我们迁移到了 Jest),这样一来,便可以确保以后的迁移不会同时更改测试和代码,于是更有信心不引入静默错误。转换了 Jasmine 测试之后,我们开始寻找生产代码中第一个转换的候选者。

在 Dropbox,我们有一种在发布功能之前进行 bug 修复的文化:QA 和团队的许多工程师会坐在一起,尝试手动找出功能的 bug。与 QA 和许多团队讨论之后,我们决定首先转换内部工具和共享链接页面的评论 UI。

然后开始转换内部崩溃报告、功能 gating 和电子邮件发送工具,接着开始大批量开始转换其余面向用户的代码库。

附带说明:因为我们最近投资采用了 Bazel 作为构建工具,并且以此工具作为我们开发和集成测试框架的基础,所以很容易确定一个 bug 是否是由更改引起的。由于我们使用 Bazel 和自己的 itest 工具提供服务,我们可以轻松查看之前的版本,并对其运行 itest。通过在代码的确切版本上重建和启动 dev 服务的副本,很容易看到错误是否是由更改引入的。Dropbox 工程师本杰明·彼得森(Benjamin Peterson)在 2017 年 Bazel 大会上发表的关于集成测试的演讲中谈到了 itest 是如何运行的。

从这里开始转换内部崩溃报告、功能门控和电子邮件发送工具,然后开始批量转换其余面向用户的代码库。

严谨的意义

编写代码转换器时我们学到的一条经验是:你必须严谨,涵盖每个角落才行。明确指出哪些内容没有覆盖是非常重要的,因为错过的任何场景都可能会出错。如果要编写自己的转换工具,请参考以下提示:

  • 每当你为一个 node 类型添加转换时,请在文档中查看需要覆盖的所有情况。
  • 如果你认为某个 node 类型不太可能出现并且不值得覆盖,请抛出一个错误;这样一来,如果它确实出现在代码中,你就不会感到惊讶了。为此,我们高度依赖 ESTree 规范 和 ts-estree 源代码。
  • 每当你发现错误时,请搜索你的代码库以查找该错误模式的其他实例并修复它们。否则,你会在生产中不停遇到类似的错误,结果焦头烂额。

尾声

在项目的最后几周,我们一次转换大约 100-200 个文件。通过改进工具,让这种规模的转换可以在几个小时的工程时间内完成。这意味着可以在一两天内就从零开始集成到主分支中,尽量降低重新部署的开销。大部分时间都花在类型检查和调整上了,因为在前期验证工作中已经解决了 Jasmine 和 Selenium 测试的大多数问题。

我们的一个技巧是在代码库上运行 tsc --noEmit --watch 快速迭代,这样就可以在大约 10 秒内获得增量类型检查结果。之所以能这么快,部分是因为在迁移过程中从 TypeScript 2.5 升级到了 2.6,后者大幅提升了 --watch 的速度。

为了保持专注,我们还在团队区的白板上写上了剩余的 CoffeeScript 文件的计数,并在每次将代码合并到 master 分支时更新数据。

转换完最后的 CoffeeScript 之后,我们与内部客户一起畅饮咖啡,欢送 CoffeeScript。

只有两个错误

我们一开始就知道,如果引发了太多错误,整个项目最后都会报销。结果,我只记得有两个错误进入了生产环境。大多数潜在错误是在手动修复类型检查错误时引入的,尽管我们的测试覆盖率不高,但它们并没有闯过我们 Jasmine 和 Selenium 测试的考验。

因此,大多数团队除了意识到他们的代码现在是 TypeScript 之外,并没有感到有什么变化。虽然他们需要重做一些工作,但他们很满意新的 TypeScript 环境,因此我们没有收到太多抱怨。

我们最后才转换那些最担心出问题的团队的代码,这样就能用之前零错误的表现说服他们了。但有一个团队还是不放心,于是我们承诺说:即便出现了重大错误,我们也会 24 小时快速响应并修复(只要他们告诉我们如何重现),还会在一个工作日内解决次要错误。

之所以做出这一承诺,是因为我们对转换脚本充满信心。结果他们并没有遇到重大错误,唯一一个小错误我们也是在异常报告中发现的,在他们第二天上班之前就解决掉了。

还有一些错误一开始他们说是我们的转换造成的,但最后都被我们证明来自于其他原因。

回顾

最终,自动迁移过程仅花费了大约两个月时间,有三名工程师参与,花费了大约 19 个工程师周。当然,迁移输出的不是大多数人最初想要的理想的 TypeScript,而是一些杂乱无章,遍布 any 的 TypeScript。

这一代价是值得的。它让我们更快地摆脱了 CoffeeScript,这样就不用继续支持 CoffeeScript,也不用让新员工学习这种语言。可以在所有地方使用 TypeScript,同时逐步改进代码样式和类型安全。

在整个过程中我们吸取了很多技术教训,其中可能最重要的教训是:应该将政治和组织资源省下来,用在不能为所有人自动化的那些任务上。尽管没有人特别喜欢 CoffeeScript,而且有些团队可能已经自愿将代码转换为 TypeScript,但让其他人在一年时间里手动转换到 TypeScript 的要求太不切实际了。

事后看来,我们应该尽量自动化那些重复性的劳动,遇到无法自动化,真正需要专业编程知识的问题时才去动用宝贵的人力资源。

现今

后记:快进到 2020 年,Dropbox 已经有了 200 万行 TypeScript 代码。我们的整个代码库都是静态类型的,并且内部有一个繁荣的 TypeScript 社区。TypeScript 使我们能够扩展工程组织,使各个团队可以独立工作,同时在整个代码库中保持清晰的联系。

TypeScript 这种语言已迅速普及,我们很幸运能成为最早迁移的大公司之一。因此我们得以发展这一领域的专业知识并与外界分享。我们的 JS 公会定期分享 TypeScript 的技巧和窍门,我们的工程师喜欢他们使用的语言。一位工程师甚至撰写了一份案例研究,总结 TypeScript 不是 JavaScript 严格超集的那些情况。

仍然有少数文件带有“此文件从 coffeescript 迁移过来”的注释,但这些文件仅占代码库的一小部分。我们现在的代码有良好的类型,并且一般会 push back 那些 any。最近,我们将所有代码库都升级到了 TypeScript 3.8。——Matthew Gerstman

关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书!


档描述

本文是关注微信小程序的开发和面试问题,

由基础到困难循序渐进,

适合面试和开发小程序。

并有热点框架(vue react node.js 全栈)前端资源以及后端视频资源和源码

并基于前端进阶和面试的需求 总结了常用插件和js算法

以及html/css 和js热点面试题

并总结了热点React/ES6/Vue面试题

Vue面试题

生命周期函数面试题

1.什么是 vue 生命周期

2.vue生命周期的作用是什么

3.第一次页面加载会触发哪几个钩子

4.简述每个周期具体适合哪些场景

5.created和mounted的区别

6.vue获取数据在哪个周期函数

7.请详细说下你对vue生命周期的理解?

vue路由面试题

1.mvvm 框架是什么?

2.vue-router 是什么?它有哪些组件

3.active-class 是哪个组件的属性?

4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值

5.vue-router 有哪几种导航钩子?

6.$route 和 $router 的区别

7.vue-router响应路由参数的变化

8.vue-router传参

9.vue-router的两种模式

10.vue-router实现路由懒加载( 动态加载路由 )

vue常见面试题

1.vue优点

2.vue父组件向子组件传递数据?

3.子组件像父组件传递事件

4.v-show和v-if指令的共同点和不同点

5.如何让CSS只在当前组件中起作用

6.<keep-alive></keep-alive>的作用是什么?

7.如何获取dom

8.说出几种vue当中的指令和它的用法?

9. vue-loader是什么?使用它的用途有哪些?

10.为什么使用key

11.axios及安装

12.axios解决跨域

13.v-modal的使用

14.scss的安装以及使用

15. 请说出vue.cli项目中src目录每个文件夹和文件的用法?

16.分别简述computed和watch的使用场景

17.v-on可以监听多个方法吗

18.$nextTick的使用

19.vue组件中data为什么必须是一个函数

20.vue事件对象的使用

21 组件间的通信

22.渐进式框架的理解

23.Vue中双向数据绑定是如何实现的

24.单页面应用和多页面应用区别及优缺点

25.vue中过滤器有什么作用及详解

26.v-if和v-for的优先级

27.assets和static的区别

28.列举常用的指令

29.vue常用的修饰符

30.数组更新检测

31.Vue.set视图更新

32.自定义指令详解

33.vue的两个核心点

34.vue和jQuery的区别

35 引进组件的步骤

36.Vue-cli打包命令是什么?打包后悔导致路径问题,应该在哪里修改

37.三大框架的对比

38. 跨组件双向数据绑定

39.delete和Vue.delete删除数组的区别

40.SPA首屏加载慢如何解决

41.Vue-router跳转和location.href有什么区别

42. vue slot

43.你们vue项目是打包了一个js文件,一个css文件,还是有多个文件?

44.vue遇到的坑,如何解决的?

45.Vue里面router-link在电脑上有用,在安卓上没反应怎么解决?

46.Vue2中注册在router-link上事件无效解决方法

47.RouterLink在IE和Firefox中不起作用(路由不跳转)的问题

48.axios的特点有哪些

49.请说下封装 vue 组件的过程?

50.vue 各种组件通信方法(父子 子父 兄弟 爷孙 毫无关系的组件)

51.params和query的区别

52. vue mock数据

53 vue封装通用组件

54.vue初始化页面闪动问题

55.vue禁止弹窗后的屏幕滚动

56.vue更新数组时触发视图更新的方法

57.vue常用的UI组件库

58. vue如何引进本地背景图片

59. vue如何引进sass

60.vue修改打包后静态资源路径的修改

vuex常见面试题

1.vuex是什么?怎么使用?哪种功能场景使用它?

2.vuex有哪几种属性

3.不使用Vuex会带来什么问题

4.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

5.vuex一个例子方法

6.Vuex中如何异步修改状态

7.Vuex中actions和mutations的区别

vue项目实战

1.顶部悬停效果

2.电话本列表效果( 右边字母分类 上下滑动 旁边字母显示高亮)

3.vue做代理

4.Vue路由切换时的左滑和右滑效果示例

ES6面试题

ES6新增方法面试题

1.let const var比较

2.反引号(`)标识

3.函数默认参数

4.箭头函数

5.属性简写

6.方法简写

7.Object.keys()方法,获取对象的所有属性名或方法名

8.Object.assign ()原对象的属性和方法都合并到了目标对象

9.for...of 循环

10.import和export

11.Promise对象

12.解构赋值

13.set数据结构(可用于快速去重)

14.Spread Operator 展开运算符(...)

15.字符串新增方法

ES6数组面试题

1.forEach()

2.map()

3.filter()

4.reduce()

5.some()

6.every()

7.all()方法

ES6编程题

1.使用解构,实现两个变量的值的交换

2.利用数组推导,计算出数组 [1,2,3,4] 每一个元素的平方并组成新的数组。

3.使用ES6改下面的模板

4.把以下代码使用两种方法,来依次输出0到9?

react面试题

react生命周期面试题

1.react 生命周期函数

2.react生命周期中,最适合与服务端进行数据交互的是哪个函数

3.运行阶段生命周期调用顺序

4.shouldComponentUpdate 是做什么的,(react 性能优化是哪个周期函数?)

5.指出(组件)生命周期方法的不同

react 基础面试题

1.React 中 keys 的作用是什么?

2.React 中 refs 的作用是什么?

3.React 中有三种构建组件的方式

4.调用 setState 之后发生了什么?

5.react diff 原理(常考,大厂必考)

6.为什么建议传递给 setState 的参数是一个 callback 而不是一个对象

7.除了在构造函数中绑定 this,还有其它方式吗

8.setState第二个参数的作用

9.(在构造函数中)调用 super(props) 的目的是什么

10.简述 flux 思想

11.在 React 当中 Element 和 Component 有何区别?

12.描述事件在 React 中的处理方式。

13.createElement 和 cloneElement 有什么区别?

14.如何告诉 React 它应该编译生产环境版本?

15.Controlled Component 与 Uncontrolled Component 之间的区别是什么?

react组件面试题

1.展示组件(Presentational component)和容器组件(Container component)之间有何不同

2.类组件(Class component)和函数式组件(Functional component)之间有何不同

3.(组件的)状态(state)和属性(props)之间有何不同

4.何为受控组件(controlled component)

5.何为高阶组件(higher order component)

6.应该在 React 组件的何处发起 Ajax 请求

7.react中组件传值

8.什么时候在功能组件( Class Component )上使用类组件( Functional Component )?

9.受控组件( controlled component )与不受控制的组件( uncontrolled component )有什么区别?

10.react 组件的划分业务组件技术组件?

redux面试题

1.redux中间件

2.redux有什么缺点

3.了解 redux 么,说一下 redux 把

react性能比较面试题

1.vue和react的区别

2.react性能优化的方案

3.React 项目用过什么脚手架

4.介绍一下webpack webpack

5.如果你创建了类似于下面的 Twitter 元素,那么它相关的类定义是啥样子的?

6.为什么我们需要使用 React 提供的 Children API 而不是 JavaScript 的 map?

js面试题

1.简述同步和异步的区别

2.怎么添加、移除、复制、创建、和查找节点

3.实现一个函数clone 可以对Javascript中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制

4.如何消除一个数组里面重复的元素

5.写一个返回闭包的函数

6.使用递归完成1到100的累加

7.Javascript有哪几种数据类型

8.如何判断数据类型

9.console.log(1+'2')和console.log(1-'2')的打印结果

10.Js的事件委托是什么,原理是什么

11.如何改变函数内部的this指针的指向

12.列举几种解决跨域问题的方式,且说明原理

13.谈谈垃圾回收机制的方式及内存管理

14.写一个function ,清除字符串前后的空格

15.js实现继承的方法有哪些

16.判断一个变量是否是数组,有哪些办法

17.let ,const ,var 有什么区别

18.箭头函数与普通函数有什么区别

19.随机取1-10之间的整数

20.new操作符具体干了什么

21.Ajax原理

22.模块化开发怎么做

23.异步加载Js的方式有哪些

24.xml和 json的区别

25.webpack如何实现打包的

26.常见web安全及防护原理

27.用过哪些设计模式

28.为什么要同源限制

29.offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别

30.javascript有哪些方法定义对象

31.说说你对promise的了解

32.谈谈你对AMD、CMD的理解

33.web开发中会话跟踪的方法有哪些

34.介绍js有哪些内置对象?

35.说几条写JavaScript的基本规范?

36.javascript创建对象的几种方式?

37.eval是做什么的?

38.null,undefined 的区别?

39.[“1”, “2”, “3”].map(parseInt) 答案是多少?

40.javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么?

41.js延迟加载的方式有哪些?

42.defer和async

43.说说严格模式的限制

44.attribute和property的区别是什么?

45.ECMAScript6 怎么写class么,为什么会出现class这种东西?

46.常见兼容性问题

47.函数防抖节流的原理

48.原始类型有哪几种?null是对象吗?

49.为什么console.log(0.2+0.1==0.3) //false

50.说一下JS中类型转换的规则?

51.深拷贝和浅拷贝的区别?如何实现

52.如何判断this?箭头函数的this是什么

53.== 和 ===的区别

54.什么是闭包

55.JavaScript原型,原型链 ? 有什么特点?

56.typeof()和instanceof()的用法区别

57.什么是变量提升

58.all、apply以及bind函数内部实现是怎么样的

59.为什么会出现setTimeout倒计时误差?如何减少

60.谈谈你对JS执行上下文栈和作用域链的理解

61.new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?

62.prototype 和 proto 区别是什么?

63.使用ES5实现一个继承?

64.取数组的最大值(ES5、ES6)

65.ES6新的特性有哪些?

66.promise 有几种状态, Promise 有什么优缺点 ?

67.Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ?

68.Promise和setTimeout的区别 ?

69.如何实现 Promise.all ?

70.如何实现 Promise.finally ?

71.如何判断img加载完成

72.如何阻止冒泡?

73.如何阻止默认事件?

74.ajax请求时,如何解释json数据

75.json和jsonp的区别?

76.如何用原生js给一个按钮绑定两个onclick事件?

77.拖拽会用到哪些事件

78.document.write和innerHTML的区别

79.jQuery的事件委托方法bind 、live、delegate、on之间有什么区别?

80.浏览器是如何渲染页面的?

81.$(document).ready()方法和window.onload有什么区别?

82. jquery中$.get()提交和$.post()提交有区别吗?

83.对前端路由的理解?前后端路由的区别?

84.手写一个类的继承

85.XMLHttpRequest:XMLHttpRequest.readyState;状态码的意思

正则表达式常见面试题

1.给一个连字符串例如:get-element-by-id转化成驼峰形式。

2.匹配二进制数字

3.非零的十进制数字 (有至少一位数字, 但是不能以0开头)

4.匹配一年中的12个月

5.匹配qq号最长为13为

6.匹配常见的固定电话号码

7.匹配ip地址

8.匹配用尖括号括起来的以a开头的字符串

9.分割数字每三个以一个逗号划分

10.判断字符串是否包含数字

11.判断电话号码

12.判断是否符合指定格式

13.判断是否符合USD格式

14.JS实现千位分隔符

15.获取 url 参数

16.验证邮箱

17.验证身份证号码

18.匹配汉字

19.去除首尾的'/'

20.判断日期格式是否符合 '2017-05-11'的形式,简单判断,只判断格式

21.判断日期格式是否符合 '2017-05-11'的形式,严格判断(比较复杂)

22.IPv4地址正则

23.十六进制颜色正则

24.车牌号正则

25.过滤HTML标签

26.密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符

27.URL正则

28.匹配浮点数

浏览器/html/css面试题

1.什么是盒模型

2.行内元素有哪些?块级元素有哪些? 空(void)元素有那些?行内元素和块级元素有什么区别?

3.简述src和href的区别

4.什么是css Hack

5.什么叫优雅降级和渐进增强

6.px和em的区别

7.HTML5 为什么只写

8.Http的状态码有哪些

9.一次完整的HTTP事务是怎么一个过程

10.HTTPS是如何实现加密

11.浏览器是如何渲染页面的

12.浏览器的内核有哪些?分别有什么代表的浏览器

13.页面导入时,使用link和@import有什么区别

14.如何优化图像,图像格式的区别

15.列举你了解Html5. Css3 新特性

16.可以通过哪些方法优化css3 animation渲染

17.列举几个前端性能方面的优化

18.如何实现同一个浏览器多个标签页之间的通信

19.浏览器的存储技术有哪些

20.css定位方式

21.尽可能多的写出浏览器兼容性问题

22.垂直上下居中的方法

23.响应式布局原理

25.清除浮动的方法

26.http协议和tcp协议

27.刷新页面,js请求一般会有哪些地方有缓存处理

28.如何对网站的文件和资源进行优化

29.你对网页标准和W3C重要性的理解

30.Http和https的区别

31.data-属性的作用

32.如何让Chrome浏览器显示小于12px的文字

33.哪些操作会引起页面回流(Reflow)

34.CSS预处理器的比较less sass

35.如何实现页面每次打开时清除本页缓存

36.什么是Virtual DOM,为何要用Virtual DOM

37.伪元素和伪类的区别

38.http的几种请求方法和区别

39.前端需要注意哪些SEO

40.的title和alt有什么区别

41.从浏览器地址栏输入url到显示页面的步骤

42.如何进行网站性能优化

43.语义化的理解

44.HTML5的离线储存怎么使用,工作原理能不能解释一下?

45.浏览器是怎么对HTML5的离线储存资源进行管理和加载的呢

46.iframe有那些缺点?

47.WEB标准以及W3C标准是什么?

48.Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?

49.HTML全局属性(global attribute)有哪些

50.Canvas和SVG有什么区别?

51.如何在页面上实现一个圆形的可点击区域?

52.网页验证码是干嘛的,是为了解决什么安全问题

53.请描述一下 cookies,sessionStorage 和 localStorage 的区别?

54. CSS选择器有哪些?哪些属性可以继承?

55.CSS优先级算法如何计算?

56.CSS3有哪些新特性?

57.请解释一下CSS3的flexbox(弹性盒布局模型),以及适用场景?

58.用纯CSS创建一个三角形的原理是什么?

59.常见的兼容性问题?

60.为什么要初始化CSS样式

61.absolute的containing block计算方式跟正常流有什么不同?

62.CSS里的visibility属性有个collapse属性值?在不同浏览器下以后什么区别?

63.display:none与visibility:hidden的区别?

64.position跟display、overflow、float这些特性相互叠加后会怎么样?

65.对BFC规范(块级格式化上下文:block formatting context)的理解?

66.为什么会出现浮动和什么时候需要清除浮动?清除浮动的方式?

67.上下margin重合的问题

68. 设置元素浮动后,该元素的display值是多少?

69.移动端的布局用过媒体查询吗?

70.CSS优化、提高性能的方法有哪些?

71.浏览器是怎样解析CSS选择器的?

72.在网页中的应该使用奇数还是偶数的字体?为什么呢?

73.margin和padding分别适合什么场景使用?

74.元素竖向的百分比设定是相对于容器的高度吗?

75.全屏滚动的原理是什么?用到了CSS的哪些属性?

76.什么是响应式设计?响应式设计的基本原理是什么?如何兼容低版本的IE?

77. 视差滚动效果?

78.::before 和 :after中双冒号和单冒号有什么区别?解释一下这2个伪元素的作用

79.让页面里的字体变清晰,变细用CSS怎么做?

80. position:fixed;在android下无效怎么处理?

81.如果需要手动写动画,你认为最小时间间隔是多久,为什么?

82.li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法?

83.display:inline-block 什么时候会显示间隙?

84. 有一个高度自适应的div,里面有两个div,一个高度100px,希望另一个填满剩下的高度

85.png、jpg、gif 这些图片格式解释一下,分别什么时候用。有没有了解过webp?

86.style标签写在body后与body前有什么区别?

87.CSS属性overflow属性定义溢出元素内容区的内容会如何处理?

88.阐述一下CSS Sprites

89. 一行或多行文本超出隐藏

微信小程序开发(持续更新)

初识小程序

1.注册小程序

2.微信开发者工具

3.小程序与普通网页开发的区别

4.小程序尺寸单位rpx

5.样式导入(WeUI for)

6.选择器

7.小程序image高度自适应及裁剪问题

8.微信小程序长按识别二维码

9.给页面加背景色

10.微信小程序获取用户信息

11.代码审核和发布

12.小程序微信认证

13.小程序申请微信支付

14.小程序的目录解构及四种文件类型

15.小程序文件的作用域

16.小程序常用组件

1.view

2.scroll-view

3.swiper组件

4.movable-view

5.cover-view

6.cover-image

小程序基础

17.授权得到用户信息

18.数据绑定

19.列表渲染

20.条件渲染

21.公共模板建立

22.事件及事件绑定

23.引用

24.页面跳转

1.wx.switchTab

2.wx.reLaunch

3.wx.redirectTo

4.wx.navigateTo

5.wx.navigateBack

25.设置tabBar

26.页面生命周期

27.转发分享

小程序高级

28.request请求后台接口

29.http-promise 封装

30.webview

31.获取用户收货地址

32.获取地里位置

33.自定义组件

34.微信小程序支付问题

小程序项目实战

35.微信小程序本地数据缓存

36.下拉刷新和下拉加载

37.列表页向详情页跳转(动态修改title)

38.客服电话

39.星级评分组件

40.小程序插槽的使用slot

41.模糊查询

42.wxs过滤

43.小程序动画

44.列表根据索引值渲染

45.小程序动态修改class

46.小程序常用框架

47.参数传值的方法

48.提高小程序的应用速度

49.微信小程序的优劣势

50.小程序的双向绑定和vue的区别

51.微信小程序给按钮添加动画

52.微信小程序的tab按钮的转换

53.微信小程序引进echarts

54.APP打开小程序流程

55.小程序解析富文本编辑器

小程序常见bug

1.域名必须是HTTPS

2. input组件placeholder字体颜色

3. wx.navigateTo无法跳转到带tabbar的页面

4. tabbar在切换时页面数据无法刷新

5.如何去掉自定义button灰色的圆角边框

6.input textarea是APP的原生组件,z-index层级最高

7.一段文字如何换行

8.设置最外层标签的margin-bottom在IOS下不生效

9.小程序中canvas的图片不支持base64格式

10.回到页面顶部

11.wx.setStorageSync和wx.getStorageSync报错问题

12.如何获取微信群名称?

13.new Date跨平台兼容性问题

14.wx.getSystemInfoSync获取windowHeight不准确

15.图片本地资源名称,尽量使用小写命名

移动端热点问题

1. 1px border问题

2.2X图 3X图适配

3.图片在安卓上,有些设备模糊问题

4.固定定位布局 键盘挡住输入框内容

5.click的300ms延迟问题和点击穿透问题

6.phone及ipad下输入框默认内阴影

7.防止手机中页面放大和缩小

8.flex布局

9.px、em、rem、%、vw、vh、vm这些单位的区别

10. 移动端适配- dpr浅析

11.移动端扩展点击区域

12 上下拉动滚动条时卡顿、慢

13 长时间按住页面出现闪退

14. ios和android下触摸元素时出现半透明灰色遮罩

15. active兼容处理 即 伪类:active失效

16.webkit mask兼容处理

17. pc端与移动端字体大小的问题

18. transiton闪屏

19.圆角bug

20.如何解决禁用表单后移动端样式不统一问题?

js常用插件

轮播图插件

二级城市插件

三级城市插件

文字滑动效果

手风琴效果

视频播放插件

弹层插件

百度编辑器

ACE编辑器(轻巧)

上传图片(裁剪)

页面加载效果

全选反选各种效果

京东楼层效果

懒加载

快速建站(全栈)

dedecms(文章累)

discuz(论坛)

ecshop(电商)

PHPEMS(考试)