(下篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂

引言

本篇文章会继续沿着前面两篇的脚步,继续梳理前端领域一些比较主流的进阶知识点,力求能让大家在横向层面有个全面的概念。能在面试时有限的时间里,能够快速抓住重点与面试官交流。这些知识点属于加分项,如果能在面试时从容侃侃而谈,想必面试官会记忆深刻,为你折服的~?

另外有许多童鞋提到: 面试造火箭,实践全不会,对这种应试策略表达一些担忧。其实我是觉得面试或者这些知识点,也仅仅是个初级的 开始。能帮助在初期的快速成长,但这种策略并没办法让你达到更高的水平,只有后续不断地真正实践和深入研究,才能突破自己的瓶颈,继续成长。面试,不也只是一个开始而已嘛。~?

建议各位小伙从基础入手,先看

  • (上篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂
  • (中篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂

小菜鸡博客求赞 ? blog

进阶知识

Hybrid

随着 Web技术 和 移动设备 的快速发展,在各家大厂中,Hybrid 技术已经成为一种最主流最不可取代的架构方案之一。一套好的 Hybrid 架构方案能让 App 既能拥有 极致的体验和性能,同时也能拥有 Web技术 灵活的开发模式、跨平台能力以及热更新机制。因此,相关的 Hybrid 领域人才也是十分的吃香,精通Hybrid 技术和相关的实战经验,也是面试中一项大大的加分项。

1. 混合方案简析

Hybrid App,俗称 混合应用,即混合了 Native技术 与 Web技术 进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同:

  • Webview UI:

    • 通过 JSBridge 完成 H5 与 Native 的双向通讯,并 基于 Webview 进行页面的渲染;
    • 优势: 简单易用,架构门槛/成本较低,适用性与灵活性极强;
    • 劣势: Webview 性能局限,在复杂页面中,表现远不如原生页面;
  • Native UI:

    • 通过 JSBridge 赋予 H5 原生能力,并进一步将 JS 生成的虚拟节点树(Virtual DOM)传递至 Native 层,并使用 原生系统渲染
    • 优势: 用户体验基本接近原生,且能发挥 Web技术 开发灵活与易更新的特性;
    • 劣势: 上手/改造门槛较高,最好需要掌握一定程度的客户端技术。相比于常规 Web开发,需要更高的开发调试、问题排查成本;
  • 小程序

    • 通过更加定制化的 JSBridge,赋予了 Web 更大的权限,并使用双 WebView 双线程的模式隔离了 JS逻辑 与 UI渲染,形成了特殊的开发模式,加强了 H5 与 Native 混合程度,属于第一种方案的优化版本;
    • 优势: 用户体验好于常规 Webview 方案,且通常依托的平台也能提供更为友好的开发调试体验以及功能;
    • 劣势: 需要依托于特定的平台的规范限定

2. Webviev

Webview 是 Native App 中内置的一款基于 Webkit内核 的浏览器,主要由两部分组成:

  • WebCore 排版引擎
  • JSCore 解析引擎

在原生开发 SDK 中 Webview 被封装成了一个组件,用于作为 Web页面 的容器。因此,作为宿主的客户端中拥有更高的权限,可以对 Webview 中的 Web页面 进行配置和开发。

Hybrid技术中双端的交互原理,便是基于 Webview 的一些 API 和特性。

3. 交互原理

Hybrid技术 中最核心的点就是 Native端 与 H5端 之间的 双向通讯层,其实这里也可以理解为我们需要一套 跨语言通讯方案,便是我们常听到的 JSBridge。

  • JavaScript 通知 Native

    • API注入,Native 直接在 JS 上下文中挂载数据或者方法
      • 延迟较低,在安卓4.1以下具有安全性问题,风险较高
    • WebView URL Scheme 跳转拦截
      • 兼容性好,但延迟较高,且有长度限制
    • WebView 中的 prompt/console/alert拦截(通常使用 prompt)
  • Native 通知 Javascript:

    • IOS: stringByEvaluatingJavaScriptFromString
    // Swift
    webview.stringByEvaluatingJavaScriptFromString("alert('NativeCall')")
    复制代码
    • Android: loadUrl (4.4-)
    // 调用js中的JSBridge.trigger方法
    // 该方法的弊端是无法获取函数返回值;
    webView.loadUrl("javascript:JSBridge.trigger('NativeCall')")
    复制代码
    • Android: evaluateJavascript (4.4+)
    // 4.4+后使用该方法便可调用并获取函数返回值;
    mWebView.evaluateJavascript("javascript:JSBridge.trigger('NativeCall')", 	 new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
    复制代码

4. 接入方案

整套方案需要 Web 与 Native 两部分共同来完成:

  • Native: 负责实现URL拦截与解析、环境信息的注入、拓展功能的映射、版本更新等功能;
  • JavaScirpt: 负责实现功能协议的拼装、协议的发送、参数的传递、回调等一系列基础功能。

接入方式:

  • 在线H5: 直接将项目部署于线上服务器,并由客户端在 HTML 头部注入对应的 Bridge。
    • 优势: 接入/开发成本低,对 App 的侵入小;
    • 劣势: 重度依赖网络,无法离线使用,首屏加载慢;
  • 内置离线包: 将代码直接内置于 App 中,即本地存储中,可由 H5 或者 客户端引用 Bridge。
    • 优势: 首屏加载快,可离线化使用;
    • 劣势: 开发、调试成本变高,需要多端合作,且会增加 App 包体积

5. 优化方案简述

  • Webview 预加载: Webview 的初始化其实挺耗时的。我们测试过,大概在100~200ms之间,因此如果能前置做好初始化于内存中,会大大加快渲染速度。
  • 更新机制: 使用离线包的时候,便会涉及到本地离线代码的更新问题,因此需要建立一套云端下发包的机制,由客户端下载云端最新代码包 (zip包),并解压替换本地代码。
    • 增量更新: 由于下发包是一个下载的过程,因此包的体积越小,下载速度越快,流量损耗越低。只打包改变的文件,客户端下载后覆盖式替换,能大大减小每次更新包的体积。
    • 条件分发: 云平台下发更新包时,可以配合客户端设置一系列的条件与规则,从而实现代码的条件更新:
      • 地区 更新: 例如一个只有中国地区才能更新的版本;
      • 语言 更新: 例如只有中文版本会更新;
      • 按 App 版本 更新: 例如只有最新版本的 App 才会更新;
      • 灰度 更新: 只有小比例用户会更新;
      • AB测试: 只有命中的用户会更新;
  • 降级机制: 当用户下载或解压代码包失败时,需要有套降级方案,通常有两种做法:
    • 本地内置: 随着 App 打包时内置一份线上最新完整代码包,保证本地代码文件的存在,资源加载均使用本地化路径;
    • 域名拦截: 资源加载使用线上域名,通过拦截域名映射到本地路径。当本地不存在时,则请求线上文件,当存在时,直接加载;
  • 跨平台部署: Bridge层 可以做一套浏览器适配,在一些无法适配的功能,做好降级处理,从而保证代码在任何环境的可用性,一套代码可同时运行于 App内 与 普通浏览器;
  • 环境系统: 与客户端进行统一配合,搭建出 正式 / 预上线 / 测试 / 开发环境,能大大提高项目稳定性与问题排查;
  • 开发模式:
    • 能连接PC Chrome/safari 进行代码调试;
    • 具有开发调试入口,可以使用同样的 Webview 加载开发时的本地代码;
    • 具备日志系统,可以查看 Log 信息;

详细内容由兴趣的童鞋可以看文章:

  • Hybrid App技术解析 -- 原理篇
  • Hybrid App技术解析 -- 实战篇

Webpack

1. 原理简述

Webpack 已经成为了现在前端工程化中最重要的一环,通过WebpackNode的配合,前端领域完成了不可思议的进步。通过预编译,将软件编程中先进的思想和理念能够真正运用于生产,让前端开发领域告别原始的蛮荒阶段。深入理解Webpack,可以让你在编程思维及技术领域上产生质的成长,极大拓展技术边界。这也是在面试中必不可少的一个内容。

  • 核心概念

    • JavaScript 的 模块打包工具 (module bundler)。通过分析模块之间的依赖,最终将所有模块打包成一份或者多份代码包 (bundler),供 HTML 直接引用。实质上,Webpack 仅仅提供了 打包功能 和一套 文件处理机制,然后通过生态中的各种 Loader 和 Plugin 对代码进行预编译和打包。因此 Webpack 具有高度的可拓展性,能更好的发挥社区生态的力量。
      • Entry: 入口文件,Webpack 会从该文件开始进行分析与编译;
      • Output: 出口路径,打包后创建 bundler 的文件路径以及文件名;
      • Module: 模块,在 Webpack 中任何文件都可以作为一个模块,会根据配置的不同的 Loader 进行加载和打包;
      • Chunk: 代码块,可以根据配置,将所有模块代码合并成一个或多个代码块,以便按需加载,提高性能;
      • Loader: 模块加载器,进行各种文件类型的加载与转换;
      • Plugin: 拓展插件,可以通过 Webpack 相应的事件钩子,介入到打包过程中的任意环节,从而对代码按需修改;
  • 工作流程 (加载 - 编译 - 输出)

    • 1、读取配置文件,按命令 初始化 配置参数,创建 Compiler 对象;
    • 2、调用插件的 apply 方法 挂载插件 监听,然后从入口文件开始执行编译;
    • 3、按文件类型,调用相应的 Loader 对模块进行 编译,并在合适的时机点触发对应的事件,调用 Plugin 执行,最后再根据模块 依赖查找 到所依赖的模块,递归执行第三步;
    • 4、将编译后的所有代码包装成一个个代码块 (Chuck), 并按依赖和配置确定 输出内容。这个步骤,仍然可以通过 Plugin 进行文件的修改;
    • 5、最后,根据 Output 把文件内容一一写入到指定的文件夹中,完成整个过程;
  • 模块包装:

(function(modules) {
	// 模拟 require 函数,从内存中加载模块;
	function __webpack_require__(moduleId) {
		// 缓存模块
		if (installedModules[moduleId]) {
			return installedModules[moduleId].exports;
		}
		
		var module = installedModules[moduleId] = {
			i: moduleId,
			l: false,
			exports: {}
		};
		
		// 执行代码;
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		
		// Flag: 标记是否加载完成;
		module.l = true;
		
		return module.exports;
	}
	
	// ...
	
	// 开始执行加载入口文件;
	return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })({
 	"./src/index.js": function (module, __webpack_exports__, __webpack_require__) {
		// 使用 eval 执行编译后的代码;
		// 继续递归引用模块内部依赖;
		// 实际情况并不是使用模板字符串,这里是为了代码的可读性;
		eval(`
			__webpack_require__.r(__webpack_exports__);
			//
			var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("test", ./src/test.js");
		`);
	},
	"./src/test.js": function (module, __webpack_exports__, __webpack_require__) {
		// ...
	},
 })
复制代码
  • 总结:
    • 模块机制: webpack 自己实现了一套模拟模块的机制,将其包裹于业务代码的外部,从而提供了一套模块机制;
    • 文件编译: webpack 规定了一套编译规则,通过 Loader 和 Plugin,以管道的形式对文件字符串进行处理;

2. Loader

由于 Webpack 是基于 Node,因此 Webpack 其实是只能识别 js 模块,比如 css / html / 图片等类型的文件并无法加载,因此就需要一个对 不同格式文件转换器。其实 Loader 做的事,也并不难理解: 对 Webpack 传入的字符串进行按需修改。例如一个最简单的 Loader:

// html-loader/index.js
module.exports = function(htmlSource) {
	// 返回处理后的代码字符串
	// 删除 html 文件中的所有注释
	return htmlSource.replace(//g, '')
}
复制代码

当然,实际的 Loader 不会这么简单,通常是需要将代码进行分析,构建 AST (抽象语法树), 遍历进行定向的修改后,再重新生成新的代码字符串。如我们常用的 Babel-loader 会执行以下步骤:

  • babylon 将 ES6/ES7 代码解析成 AST
  • babel-traverse 对 AST 进行遍历转译,得到新的 AST
  • 新 AST 通过 babel-generator 转换成 ES5

Loader 特性:

  • 链式传递,按照配置时相反的顺序链式执行;
  • 基于 Node 环境,拥有 较高权限,比如文件的增删查改;
  • 可同步也可异步;

常用 Loader:

  • file-loader: 加载文件资源,如 字体 / 图片 等,具有移动/复制/命名等功能;
  • url-loader: 通常用于加载图片,可以将小图片直接转换为 Date Url,减少请求;
  • babel-loader: 加载 js / jsx 文件, 将 ES6 / ES7 代码转换成 ES5,抹平兼容性问题;
  • ts-loader: 加载 ts / tsx 文件,编译 TypeScript;
  • style-loader: 将 css 代码以