1.ES6 语法
1.ES6 模块化如何使用,开发环境如何打包?
语法: import export (注意有无 default 的区别)
环境: babel 编译 ES6 语法,模块化可用 webpack 和 rollup
扩展: 说一下自己对模块化标准统一的期待
rollup 功能单一(只做模块化的打包编译),webpack 功能强大
一、babel的安装
1.npm init
使用npm初始化一个项目
2.npm install -g cnpm --registry=https://registry.npm.taobao.org
全局安装淘宝镜像 cnpm,便于后续安装插件速度更快
3.cnpm install --save-dev babel-core babel-preset-es2015 babel-preset-latest
安装一些babel编译相关的依赖包
4.创建 .babelrc 隐藏文件
{
"presets": ["es2015", "latest"],
"plugins": []
}
5.cnpm install -g babel-cli
全局安装 babel-cli
6.babel --version
检验babel-cli是否安装成功,成功的话可以正常执行babel命令,此处会输出babel版本号
7.babel src/index.js
使用babel命令,将index.js中,有关ES6+的下一代JS标准的代码转换成ES5语法,让主流浏览器都能识别
文件目录参考:
// **util1.js**
export default {
a: 1
}
// **util2.js**
export function fn1 () {
alert('fn1');
}
export function fn2 () {
alert('fn2');
}
// **index.js**
import util1 from './util1.js';
import { fn1, fn2 } from './util2.js';
console.log(util1);
fn1();
fn2();
// [1, 2, 3].map(item => item + 1);
二、webpack的基本配置
1.cnpm install --save-dev webpack webpack-cli babel-loader@7
安装webpack相关依赖包
2.配置 webpack.config.js
创建webpack.config.js文件,做一些基本配置
module.exports = {
entry: './src/index.js',
output: {
path: __dirname,
filename: './build/bundle.js'
},
modules: {
rules: [{
test: /\.js?$/,
exclude: /(node_modules)/,
loader: 'babel-loader'
}]
}
};
3.配置 package.json 中的 scripts
在 scripts 添加以下配置,便于后续执行webpack编译工作"start": "webpack",
4.运行 npm start
5.成功创建了 bundle.js 之后,在src目录下创建一个 index.html,引入 bundle.js
6.命令窗口运行 http-server -p 8881
(使用这个命令的前提是已经全局安装 http-server ),打开 http://127.0.0.1:8881/ 验证结果
三、rollup.js 的基本配置
rollup打包出来的内容会很小,这是它的一大优势
主流框架 vue.js react.js 就是使用 rollup 进行打包的
1.npm init
2.cnpm install --save-dev rollup rollup-plugin-node-resolve rollup-plugin-babel babel-core babel-plugin-external-helpers babel-preset-latest
安装rollup相关依赖包
(也可以安装指定版本的依赖包: cnpm install --save-dev rollup@0.52.3 rollup-plugin-node-resolve@3.0.0 rollup-plugin-babel@3.0.3
)
3.创建 .babelrc 隐藏文件
{
"presets": [
["latest", {
"es2015": {
"modules": false
}
}]
],
"plugins": ["external-helpers"]
}
4.配置 rollup.config.js
创建 rollup.config.js文件,做一些基本配置
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
export default {
entry: 'src/index.js',
format: 'umd',
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
})
],
dest: 'build/bundle.js'
}
5.配置 package.json 中的 scripts
在 scripts 添加以下配置,便于后续执行rollup编译工作"start": "rollup -c rollup.config.js"
6.运行 npm start
如果没有异常,则会成功创建bundle.js,是将 util1.js,util2.js,index.js 模块化打包的结果
2.Class 和普通构造函数有何区别?
(1)Class 在语法上更加贴合面向对象的写法
(2)Class 实现继承更加易读、易理解
(3)更易于写 Java 等后端语言的使用
(4)本质还是语法糖,使用 prototype
class MathHandle {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y;
}
}
const m = new MathHandle(1, 2);
console.log(m.add()); // 3
console.log(typeof MathHandle); // 'function'
console.log(MathHandle.prototype.constructor === MathHandle); // true
console.log(m.__proto__ === MathHandle.prototype); // true
2.原型的实际应用、原型扩展
1.说一个原型的实际应用?
(1)描述一下 jquery 如何使用原型
(2)描述一下 zepto 如何使用原型
(3)再结合自己的项目经验,说一个自己开发的例子
2.原型如何体现它的扩展性?
(1)说一下 jquery 和 zepto 的插件机制
(2)结合自己的开发经验,做过的基于原型的插件
my-jquery.js
(function(window) {
let jQuery = function (selector) {
return new jQuery.fn.init(selector);
};
jQuery.fn = {
css: function (key, value) {
console.log('css'); // 这里只是简单打印
},
html : function (value) {
return 'html';
}
};
let init = jQuery.fn.init = function (selector) {
let slice = Array.prototype.slice;
let dom = slice.call(document.querySelectorAll(selector));
let len = dom ? dom.length : 0;
for (let i = 0; i < len; i++) {
this[i] = dom[i];
}
this.length = len;
this.selector = selector;
};
init.prototype = jQuery.fn;
window.$ = jQuery;
})(window);
使用示例:
<script src="my-jquery.js"></script>
<script>
// 插件扩展
$.fn.getNodeName = function () {
return this[0].nodeName;
}
</script>
<script>
let $p = $('p'); // jquery 实例
$p.css('font-size', '16px'); // css 是原型方法
console.log($p.html()); // html 是原型方法
console.log($p.getNodeName()); // getNodeName 是自己扩展的方法
let $div1 = $('#div1'); // jquery 实例
$div1.css('color', '#f00'); // css 是原型方法
console.log($div1.html()); // html 是原型方法
console.log($div1.getNodeName()); // getNodeName 是自己扩展的方法
</script>
3.异步
1.什么是单线程,和异步有什么关系?
(1)单线程 - 只有一个线程,同一时间只能做一件事情,两段JS不能同时执行
(2)原因 - 避免DOM渲染的冲突:
浏览器需要渲染DOM
JS可以修改DOM结构
JS执行的时候,浏览器DOM渲染会暂停
两段JS也不能同时执行(都修改DOM就冲突了)
webworker 支持多线程,但是不能访问DOM
(3)解决方案 - 异步
问题1:没按照书写方式执行,可读性差
问题2:callback中不容易模块化
2.什么是event-loop?
(1)事件轮询,JS实现异步的具体解决方案
(2)同步代码,直接执行
(3)异步函数先放在 异步队列 中
setTimeout 没有延时,立刻放入异步队列
setTimeout 有延时,延时过后放入异步队列
ajax,待ajax加载完成后放入异步队列
(4)待同步函数执行完毕,轮询执行 异步队列 的函数
// ajax请求请求成功之后被放入异步队列
$.ajax({
url: './data.json',
success: function (res) {
console.log(res);
console.log('a');
}
});
// 1000ms 之后被放入异步队列
setTimeout(function () {
console.log('b');
}, 1000);
// 立刻被放入异步队列
setTimeout(function () {
console.log('c');
});
// 主进程
console.log('d');
3.是否用过jquery的Deferred?
(1)jQuery 1.5 的变化
无法改变JS异步和单线程的本质
只能从写法上杜绝 callback 这种形式
它是一种语法糖形式,但是解耦了代码
很好的体现:开放封闭原则
// jquery 1.5 版本之前的写法:
let ajax = $.ajax({
url: './data.json',
success: function (res) {
console.log('success 1');
console.log('success 2');
console.log('success 3');
},
error: function () {
console.log('error 1');
}
});
// jquery 1.5 版本之后的写法:
let ajax = $.ajax('./data.json');
ajax.then(function () {
console.log('success a');
}, function () {
console.log('error 1');
}).then(function () {
console.log('success b');
}, function () {
console.log('error 2');
});
(2)jQuery Deferred,如何简单的封装、使用?
// 对 Deferred 的简单封装
function waitHandle() {
// 定义
let dtd = $.Deferred();
let wait = function (dtd) {
let task = function () {
console.log('执行完成');
dtd.resolve(); // 成功
// dtd.reject(); // 失败
};
setTimeout(task, 2000);
// wait 返回
// 注意,这里返回的是 promise 对象,而不是直接返回 deferred 对象
return dtd.promise();
};
// 最终返回
return wait(dtd);
}
let w = waitHandle(); // promise 对象
// w.reject(); // 报错,w.reject is not a function
$.when(w).then(function () {
console.log('success 1');
}, function () {
console.log('error 1');
});
$.when(w).then(function () {
console.log('success 2');
}, function () {
console.log('error 2');
});
// 1.总结,dtd的API可分为两类,用意不同
// 2.第一类:dtd.resolve dtd.reject,这是主动触发的函数
// 3.第二类:dtd.then dtd.done dtd.fail,这是被动监听的函数
// 4.这两类应该分开,用意不同,否则后果很严重
// 5.可以在上面代码创建实例之后执行 w.reject() 试一下,会报错
(3)初步引入 Promise 概念,promise 和 Deferred 的区别?
最主要的区别就是,Deferred 对象有主动修改和被动监听的函数,它们混在了一起,容易被外部篡改。
promise 对象只能被动监听,不能主动修改。
4.Promise 的基本使用和原理?
(1)基本语法
(2)如何捕获异常
(3)多个串联 - 链式执行的好处
(4)Promise.all 和 Promise.race
(5)Promise 标准 - 状态变化,then函数
5.介绍一下 async/await (和Promise的区别、联系)
(1)基本语法
(2)使用了 Promise,并没有和 Promise 冲突
(3)完全是同步的写法,再也没有回调函数
(4)但是:改变不了 JS 单线程、异步的本质
6.总结一下当前JS解决异步的方案
(1)jQuery Deferred
(2)Promise
(3)async/await
(4)还有一个,Generator,可以解决异步,但是为什么不用它:
原理比较复杂
不是异步的直接替代方式
有更好更简洁的解决方案 async/await
koa框架 也由 Generator 转向 async/await
4.虚拟 DOM
1.vdom 是什么?为何会存在vdom?
(1)virtual dom,虚拟DOM
(2)用JS模拟DOM结构
(3)DOM变化的对比,放在JS层来做(图灵完备语言才能做),提高效率
(4)提高重绘性能
为何会存在vdom?
(1)DOM操作是“昂贵”的,JS运行效率高
(2)尽量减少DOM操作,而不是“推到重来”
(3)项目越复杂,影响越严重
(4)vdom即可解决这个问题
<!--真实的dom结构-->
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
</ul>
// 对应的 vnode 基本结构
let vnode = {
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: { className: 'item' },
children: ['Item 1']
},
{
tag: 'li',
attrs: { className: 'item' },
children: ['Item 2']
},
]
};
2.vdom 如何应用,核心API是什么?
如何使用?使用 snabbdom 的用法来举例
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
let snabbdom = window.snabbdom;
// 定义 patch
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners,
]);
// 定义 h
let h = snabbdom.h;
let container = document.getElementById('container');
// 使用h函数通过 vdom 生成 vnode
let vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2'),
]);
// 第一次渲染
patch(container, vnode);
document.getElementById('btn-change').addEventListener('click', function () {
// 按钮点击之后,模拟数据变化,生成 newVnode
let newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item B'),
h('li.item', {}, 'Item 3'),
]);
patch(vnode, newVnode);
});
</script>
核心API:h函数,patch函数
h('<标签名>', {...属性...}, [...子元素...]);
h('<标签名>', {...属性...});
patch(container, vnode);
patch(vnode, newVnode);
3.介绍一下diff算法
(1)什么是 diff 算法
是linux的基础命令,是git的基本工具
(2)去繁就简
diff算法非常复杂,实现难度很大,源码量很大
去繁就简,讲明白核心流程,不关心细节
大部分人都不清楚细节,但是要关心核心流程
去繁就简之后,依然具有很大挑战性,并不简单
(3)vdom为何要用diff算法
DOM操作是“昂贵”的,因此尽量减少DOM操作
找出本次DOM必须更新的节点来更新,其他的不更新
这个“找出”的过程,就需要diff算法
(4)diff算法的实现流程
patch(container, vnode) 和 patch(vnode, newVnode)
createElement() 和 updateChildren()
function createElement (vnode) {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if (tag == null) return null;
// 创建真实 DOM 元素
let elem = document.createElement(tag);
// 给 elem 添加属性
for (let attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName]);
}
}
// 给 elem 添加子元素
children.forEach(function (childVnode){
elem.appendChild(createElement(childVnode)); // 递归
});
return elem;
}
function updateChildren(vnode, newVnode) {
let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach(function (childVnode, index) {
let newChildVnode = newChildren[index];
if (childVnode.tag === newChildVnode.tag) {
// 深层次对比,递归
updateChildren(childVnode, newChildVnode);
} else {
// 替换
replaceNode(childVnode, newChildVnode);
}
});
}
function replaceNode(vnode, newVnode) {
let elem = vnode.elem; // 真实的dom节点
let newElem = createElement(newVnode);
// 替换
}
5.MVVM 和 vue
1.说一下使用jQuery和使用vue的区别
(1)数据和视图的分离,解耦(开放封闭原则)
(2)以数据驱动视图,只关心数据变化,DOM操作被封装
使用jQuery写一个 todo-list demo
<div>
<input type="text" name="" id="txt-title">
<button id="btn-submit">submit</button>
</div>
<div>
<ul id="ul-list"></ul>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
var $txtTitle = $('#txt-title');
var $btnSubmit = $('#btn-submit');
var $ulList = $('#ul-list');
$btnSubmit.click(function () {
var title = $txtTitle.val();
if (!title) return;
var $li = $('<li>' + title + '</li>');
$ulList.append($li);
$txtTitle.val('');
});
</script>
使用vue写一个 todo-list demo
<div id="app">
<div>
<input type="text" v-model="title">
<button @click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</div>
<script src="vue-2.5.13.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
title: '',
list: []
},
methods: {
add: function () {
this.list.push(this.title);
this.title = '';
}
}
});
</script>
2.说一下对MVVM的理解
(1)MVVM - Model View ViewModel
(2)三者的联系,以及如何对应到各段代码(todo-list例子)
(3)ViewModel 的理解,联系 View 和 Model
Vue三要素
(1)响应式:vue 如何监听到data的每个属性变化?
(2)模板引擎:vue 的模板如何被解析,指令如何处理?
(3)渲染:vue 的模板如何被渲染成 html?以及渲染过程
3.vue中如何实现响应式
(1)什么是响应式?
修改 data 属性之后,vue立刻监听到
data 属性被代理到 vm 上
(2)Object.defineProperty
var vm = {};
var data = {
name: 'lxcan',
age: 18
};
var key;
for (key in data) {
(function(key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get', data[key]);
return data[key];
},
set: function (newVal) {
console.log('set', newVal);
data[key] = newVal;
}
});
})(key);
}
4.vue中如何解析模板
(1)模板是什么?
本质:字符串
有逻辑,如 v-if v-for 等,嵌入了JS变量
与 html 格式很像,但有很大区别
最终还要转换为 html 来显示
模板最终必须转换成JS代码,因为:
有逻辑(v-if v-for),必须用 JS 才能实现
转换为 html 渲染页面,必须用 JS 才能实现
因此,模板最终要转换成一个 JS 函数(render 函数)
render函数小demo
<div id="app">
<p>{{price}}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
price: 100
}
});
// 手写 render 函数
function render() {
with (this) { // this 就是 vm。使用了with
return _c(
'div',
{
attrs: { 'id': 'app' }
},
[
_c('p', [_v(_s(price))])
]
)
}
}
// 相当于
function render1() {
return vm._c(
'div',
{
attrs: { 'id': 'app' }
},
[
vm._c('p', [vm._v(vm._s(vm.price))])
]
)
}
</script>
(2)render 函数
根据 todo-list demo 的 render 函数:
v-model 是怎么实现的?(双向数据绑定)
v-on:click 是怎么实现的?(绑定click事件)
v-for 是怎么实现的?(执行 _l() 函数,返回数组)
function render() {
with (this) { // this 就是 vm
return _c(
'div',
{
attrs: {"id": "app"}
},
[
_c(
'div',
[
_c(
'input',
{
directives: [
{
name: "model",
rawName: "v-model",
value: (title),
expression: "title"
}
],
attrs: {"type": "text"},
domProps: {
"value": (title)
},
on: {
"input": function ($event) {
if ($event.target.composing) return;
title = $event.target.value
}
}
}
),
_v(" "),
_c(
'button',
{
on: {
"click": add
}
},
[_v("submit")]
)
]
),
_v(" "),
_c(
'div',
[
_c(
'ul',
_l((list), function (item) {
return _c('li', [_v(_s(item))])
})
)
]
)
]
)
}
}
(3)render 函数与 vdom
vm._c 是什么?render 函数返回了什么?
vm._c 其实就相当于 snabbdom 中的 h 函数
render 函数执行之后,返回的是 vnode
vm._update = function(vnode) {
const prevVnode = vm._vnode;
vm._vnode = vnode;
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode);
} else {
vm.$el = vm.__patch__(prevVnode, vnode);
}
};
function updateComponent() {
vm._update(vm._render()); // vm._render 即 render 函数,返回了 vnode
}
updateComponent 中实现了 vdom 的 patch
页面首次渲染执行 updateComponent
data 中每次修改属性,执行 updateComponent
总结:
(1)模板:字符串,有逻辑,嵌入了JS变量
(2)模板必须转换为 JS 代码(因为 有逻辑、要渲染html、有JS变量)
(3)render 函数是什么样子的(price、todo-list)
(4)render 函数执行返回 vnode
(5)updateComponent
5.vue的整个实现流程
(1)第一步:解析模板成 render 函数
with 的用法
模板中的所有信息都被 render 函数包含
模板中用到的 data 中的属性,都变成了JS变量
模板中的 v-model v-for v-on 都变成了JS逻辑
render 函数返回 vnode
(2)第二步:响应式开始监听
Object.defineProperty 将 data 的属性代理到 vm 上
(3)第三步:首次渲染,显示页面,且绑定依赖
初次渲染,执行 updateComponent ,执行 vm.render()
执行 render 函数,会访问到 vm.list 和 vm.title
会被响应式的 get 方法监听到
为何要监听get,直接监听set不行吗?
data 中有很多属性,有些被用到,有些可能不被用到
被用到的会走到get,不被用到的不会走到get
未走到get中的属性,set的时候我们也无需关心
可以避免不必要的重复渲染
执行 updateComponent,会走到 vdom 的 patch 方法
patch 将 vnode 渲染成 DOM,初次渲染完成
(4)第四步:data 属性变化,触发 rerender
修改属性,被响应式的set监听到
set中执行 updateComponent(异步执行)
updateComponent 重新执行 vm._render()
生成的 vnode 和 preVnode,通过 patch 进行对比
渲染到 html 中
6.组件化和 React
1.说一下对组件化的理解?
(1)组件的封装:封装视图、数据、变化逻辑(数据驱动视图变化)
(2)组件的复用:props传递、复用
2.JSX语法的本质
(1)html的形式
(2)引入JS变量和表达式
(3)if...else...
(4)循环
(5)style和className
(6)事件
JSX解析
// JSX代码
var profile = <div>
<img src="avator.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;
// 解析结果
var profile = React.createElement('div', null,
React.createElement('img', {src: 'avator.png', className: 'profile'}),
React.createElement('h3', null, [user.firstName, user.lastName].join(' ')),
);
JSX 其实是语法糖
开发环境会将JSX编译成JS代码
JSX的写法大大降低了学习成本和编码工作量
同时,JSX也会增加debug成本
JSX => 独立的标准
JSX是React引入的,但不是React独有的
React已经将它作为一个独立标准开放,其他项目也可用
React.createElement 是可以自定义修改的
标准说明:本身功能已经完备;和其他标准兼容和扩展没问题
3.JSX和vdom的关系
(1)分析:为何需要vdom?
vdom 是 React 初次推广开来的,结合 JSX
JSX 就是模板,最终要渲染成html,数据驱动视图
初次渲染 + 修改 state 后的 re-render
正好符合 vdom 的应用场景
(2)React.createElement 和 h 函数,最终都返回 vnode
(3)何时 patch
初次渲染 - ReactDOM.render(<App/>, container)
会触发 patch(container, vnode)
re-render - setState
会触发 patch(vnode, newVnode)
(4)自定义组件的解析
初始化实例,然后执行 render
// JSX代码
// return (
// <div>
// <Input addTitle={this.addTitle.bind(this)}/>
// <List data={this.state.list} />
// </div>
// )
// 解析之后的代码
// return React.createElement('div', null,
// React.createElement(Input, {addTitle: this.addTitle.bind(this)}),
// React.createElement(List, {data: this.state.list}),
// )
'div' - 直接渲染 <div> 即可,vdom可以做到
Input 和 List ,是自定义组件(class),vdom 默认不认识
因此 Input 和 List 定义的时候必须声明 render 函数
根据 props 初始化实例,然后执行实例的 render 函数
render 函数返回的还是 vnode 对象
4.说一下 React setState 的过程
(1)setState 的异步(效果、原因)
setState 为何需要异步?
可能会一次执行多次 setState
你无法规定、限制用户如何使用 setState
没必要每次 setState 都重新渲染,考虑性能
即便是每次重新渲染,用户也看不到中间的效果(JS执行的时候DOM渲染是阻塞的,单线程)
只看到最后的结果即可
(2)vue 修改属性也是异步(效果、原因)
效果、原因和 setState 一样
对比记忆,印象深刻
(3)setState 的过程
每个组件实例,都有 renderComponent 方法(继承自 Component 类)
执行 renderComponent 会重新执行实例的render
render 函数返回 newVnode,然后拿到 preVnode
最终执行 patch(preVnode, newVnode)
5.React vs Vue,怎么做技术选型
前言:
技术选型没有绝对的对错,技术选型要考虑的因素非常多,你要有自己的主见就行,只要能说出合理的理由。
(1)两者的本质区别
vue - 本质是 MVVM 框架,由 MVC 发展而来
React - 本质是前端组件化框架,由后端组件化发展而来
但这并不妨碍他们两者都能实现相同的功能
(2)看模板和组件化的区别
vue - 使用模板(最初由 angular 提出)
React - 使用 JSX
模板语法上,我更加倾向于 JSX
模板分离上,我更加倾向于 vue
模板的区别:
模板应该和 JS 逻辑分离(开放封闭原则)
组件化的区别:
React 本身就是组件化,没有组件化就不是 React
vue 也支持组件化,不过是在 MVVM上的扩展
查阅 vue 组件化的文档,洋洋洒洒很多还挺复杂(侧面反映)
对于组件化,我更加倾向于 React,做的彻底而清晰
(3)两者共同点
都支持组件化
都是数据驱动视图
(4)总结问题答案
国内使用,首推 vue。文档更易读、易学、社区够大。(团队的技术不太扎实,没有框架经验,首推 vue)
如果团队水平较高经验较丰富,推荐使用 React。组件化和JSX,组件化做得彻底而清晰,JSX也趋向于标准化
7.hybrid
前言
移动端占大部分流量,已经远远超过PC
一线互联网公司都有自己的APP
这些APP中有很大比例的前端代码
那微信举例,你每天浏览微信的内容,多少是前端?
1.hybrid 是什么,为何用 hybrid?
(1)hybrid 文字解释
hybrid 即“混合”,即前端和客户端的混合开发
需前端开发人员和客户端开发人员配合完成
某些环节也可能涉及到 server 端
(2)存在价值,为何会用 hybrid
可以快速迭代更新【关键】(无需app审核,思考为何?)
体验流畅(和NA的体验基本类似)
减少开发和沟通成本,双端公用一套代码
(3)webview
是app中的一个组件(app可以有webview,也可以没有)
用于加载 h5 页面,即一个小型的浏览器内核
(4)file:// 协议
其实在一开始接触 html 开发,就已经使用了 file 协议
只不过你当时没有 “协议” “标准” 等这些概念
再次强调 “协议” “标准” 的重要性!!!
file 协议和 http 协议区别
file 协议:本地文件,快
http(s) 协议:网络加载,慢
(5)hybrid 实现流程
不是所有场景都适合使用 hybrid
使用NA:体验要求极致,变化不频繁(如头条的首页)
使用hybrid:体验要求高,变化频繁(如头条的新闻详情页)
使用h5:体验无要求,不常用(如举报、反馈等页面)
前端做好静态页面(html js css),将文件交给客户端
客户端拿到前端静态页面,以文件形式存储在app中
客户端在一个 webview 中,使用file协议加载静态页面
解答:
hybrid 是客户端和前端的混合开发
hybrid 存在的核心意义在于快速迭代,无需审核
hybrid 实现流程(图),以及 webview 和 file 协议
2.介绍一下 hybrid 更新和上线的流程?
前提:
要替换每个客户端的静态文件
只能客户端来做(客户端是我们开发的)
客户端去 server 下载最新的静态文件
我们维护 server 的静态文件
完整流程:
分版本,有版本号,如 201910030916
将静态文件压缩成 zip 包,上传到服务端
客户端每次启动,都去服务端检查版本号
如果服务端版本号大于客户端版本号,就去下载最新的 zip 包
下载完之后解压包,然后将现有文件覆盖
掌握流程图
要点1:服务端的版本和 zip 包维护
要点2:更新 zip 包之前,先对比版本号
要点3:zip 下载解压和覆盖
3.hybrid 和 h5 的主要区别?
(1)优点:
体验更好,跟NA体验基本一致
可快速迭代,无需app审核(关键)
(2)缺点:
开发成本高。联调、测试、查bug都比较麻烦
运维成本高。参考更新上线的流程
(3)适用的场景:
hybrid:产品的稳定功能,体验要求高,迭代频繁
h5:单次的运营活动(如xx红包)或不常用功能
优点:体验好,可快速迭代
缺点:开发成本高,运维成本高
适用的场景:hybrid 适合产品型,h5 适用运营型
4.前端 JS 和客户端如何通讯?
(1)回顾遗留问题
新闻详情页适用 hybrid ,前端如何获取新闻内容?
不能用 ajax 获取。第一跨域(还能解决),第二速度慢
客户端获取新闻内容,然后JS通讯拿到内容,再渲染
(2)JS 和客户端通讯的基本形式
JS 访问客户端能力,传递参数和回调函数
客户端通过回调函数返回内容
(3)schema 协议简介和使用
schema 协议——前端和客户端通讯的约定
<button id="btn">扫一扫</button>
<script>
function invokeScan() {
window['_invoke_scan_callback_'] = function (result) {
alert(result);
};
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'weixin://dl/scan?k1=v1&k2=v2&callback=_invoke_scan_callback_';
var body = document.body;
body.appendChild(iframe);
setTimeout(function () {
body.removeChild(iframe);
iframe = null;
});
}
document.getElementById('btn').addEventListener('click', function () {
invokeScan();
});
</script>
(4)schema 使用的封装
invoke.js
(function (window) {
// 调用 schema 的封装
function _invoke (action, data, callback) {
// 拼接 schema 协议
var schema = 'myapp://utils/' + action;
// 拼接参数
schema += '?a=a';
var key;
for(key in data) {
if (data.hasOwnProperty(key)) {
schema += '&' + key + '=' + data[key];
}
}
// 处理 callback
var callbackName = '';
if (typeof callback === 'string') {
callbackName = callback;
} else {
callbackName = action + Date.now();
window[callbackName] = callback;
}
schema += '&callback=' + callbackName;
// 触发
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = schema; // 重要
var body = document.body;
body.appendChild(iframe);
setTimeout(function () {
body.removeChild(iframe);
iframe = null;
});
}
// 暴露到全局变量
window.invoke = {
share: function (data, callback) {
_invoke('share', data, callback);
},
scan: function (data, callback) {
_invoke('scan', data, callback);
},
login: function (data, callback) {
_invoke('login', data, callback);
},
}
})(window);
<button id="btn1">扫一扫</button>
<button id="btn2">分享</button>
<script src="invoke.js"></script>
<script>
document.getElementById('btn1').addEventListener('click', function () {
window.invoke.scan({}, function () {});
});
document.getElementById('btn2').addEventListener('click', function () {
window.invoke.share({
title: 'xxx',
content: 'yyy'
}, function (result) {
if (result.errno === 0) {
alert('分享成功');
} else {
alert(result.message);
}
});
});
</script>
(5)内置上线
将封装的代码打包,叫做 invoke.js ,内置到客户端
客户端每次启动 webview ,都默认执行 invoke.js
本地加载,免去网络加载的时间,更快
本地加载,没有网络请求,黑客看不到 schema 协议,更安全
解答:
通讯的基本形式:调用能力,传递参数,监听回调
对 schema 协议的理解和使用
调用 schema 代码的封装
内置上线的好处:更快、更安全
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。