ES6
let/const
块级作用域
- 代码是用 '{}' 包起来的,在 '{}' 里面就是一个块级作用域 for(let i=1; i < 3; i++) { console.log(i) // 1 2 } // console.log(i) // ReferenceError: i is not defined // ES6强制开启严格模式 'use script',变量未声明不能引用,ReferenceError
let
- 变量只在自己的块级作用域内有效
- 声明的变量不能重复定义 let a = 1 let a = 2 // ERROR in ./app/js/class/lesson1.1.js
const
- 声明的常量不能修改
- 只在自己的块级作用域内有效
- 声明时必须赋值
- 声明的常量为引用类型时,只要引用类型的指针地址不变,引用类型内的数据可以改变 // const PI // SyntaxError:E:/PracticeSublime/Practice/ES6.1/app/js/class/lesson1.1.js: const PI = 3.1415926 const k = { a: 1 } k.b = 2 // PI = 2 // ERROR in ./app/js/class/lesson1.1.js console.log(PI,k) // 3.1415926
解构赋值
基本使用
-
等号左边一个结构,右边一个结构,对应位置进行赋值
let a,b [a,b] = [1,2] // console.log(a,b) // 1 2
数组解构赋值
let a,b,rest
// ... 解构赋值的一个重要特性
[a,b,...rest] = [1,2,3,4,5,6,7,8]
// console.log(a,b,rest) // 1 2 (6) [3, 4, 5, 6, 7, 8]
// 应用场景 - 默认值
let a,b,c
[a,b,c=3] = [1,2]
console.log(a,b,c) // 1 2 3
// 应用场景 - 变量交换/函数取值
let a = 1;
let b = 2;
[a,b] = [b,a] // console.log(a,b) // 2 1
function f() {
return [1,2,3,4,5,6,7,8]
}
let a,b,c
[a,,b,...c] = f()
// console.log(a,b,c) // 1 3 (5) [4, 5, 6, 7, 8]
对象解构赋值
let o = {p:42,q:true}
let {p,q} = o // console.log(p,q) // 42 true
// 应用场景 - 默认值
let {a = 10, b = 5} = {a:3} // console.log(a,b) // 3 5
let metaData = {
title: 'abc',
test: [{
title: 'test',
desc: 'description'
}]
}
let {title:esTitle,test:[{title:cnTitle}]} = metaData
console.log(esTitle,cnTitle) // abc test
正则扩展
创建正则实例
// ES5
let regex = new RegExp('xyz', 'i')
let regex2 = new RegExp(/xyz/i)
// console.log(regex.test('xyz123'), regex2.test('xyz123')) // true true
// ES6
let regex3 = new RegExp(/xyz/ig, 'i') // 后面的修饰符会覆盖前面的修饰符
// flags ES6中获取正则的修饰符
// console.log(regex3.test('xyz123'),regex3.flags) // true "i"
y&g修饰符
- 都是全局匹配
- g 是只要有匹配就成功
- y 第一个不能匹配成功就失败
- sticky 用来检测正则是否为 y 匹配模式 let regex = /b+/g; let regex1 = /b+/y; console.log('one',regex.exec(s), regex1.exec(s)) // one ["bbb", index: 0, input: "bbb_bb_b", groups: undefined] ["bbb", index: 0, input: "bbb_bb_b", groups: undefined] console.log('two',regex.exec(s), regex1.exec(s)) // two ["bb", index: 4, input: "bbb_bb_b", groups: undefined] null console.log(regex.sticky,regex1.sticky) // false true
字符串扩展
console.log('a', `\u0061`) // a a
console.log('s', `\u20BB7`) // s ₻7
console.log('s', `\u{20BB7}`) // s 𠮷 当Unicode编码大于0xFFFF编码时,使用大括号将Unicode包起来
-
codePointAt 取字符的码值
// 每两个字节为一个长度 let s = '𠮷' // 四个字节 console.log('length', s.length) // length 2 // charAt() 取字符 charCodeAt() 取码值 console.log('0', s.charAt(0)) // 0 � console.log('1', s.charAt(1)) // 1 � console.log('0', s.charCodeAt(0)) // 0 55362 console.log('1', s.charCodeAt(1)) // 1 57271 // codePointAt 取码值 let s1 = '𠮷a' console.log('length', s1.length) // length 3 console.log('code0', s1.codePointAt(0)) // code0 134071 十进制 console.log('code0', s1.codePointAt(0).toString(16)) // code0 20bb7 十六进制 console.log('code1', s1.codePointAt(1)) // code1 57271 console.log('code2', s1.codePointAt(2)) // code2 97
-
fromCodePoint 码值转换为字符 // ES5 console.log(String.fromCharCode("0x20bb7")) // ஷ // ES6 console.log(String.fromCodePoint("0x20bb7")) // 𠮷
-
ES6 字符串遍历器 for of let str = '\u{20bb7}abc' for(let i = 0; i < str.length; ++i) { console.log('es5', str[i]) // es5 � es5 � es5 a es5 b es5 c } for(let code of str) { // es6 字符串遍历器 console.log('es6', code) // es6 𠮷 es6 a es6 b es6 c }
-
模板字符串 let name = 'list' let info = 'hello world' let m =
I am ${name}, ${info}console.log(m) // I am list, hello world -
标签模板 let user = { name: 'list', info: 'hello world' }; /** * 1. 防止 xss攻击 * 2. 处理多语言转换 */
console.log(abc
I am ${user.name}, ${user.info}) // I am ,, ,listhello world function abc(s,v1,v2) { console.log(s,v1,v2) // (3) ["I am ", ", ", "", raw: Array(3)] "list" "hello world" return s+v1+v2 }
数组扩展
- Array.of() 将一组数据转换为数组
```javascript
let arr = Array.of(3,7,8,9)
console.log(arr) // (4) [3, 7, 8, 9]
let arr1 = Array.of()
console.log(arr1) // []
-
Array.from() 将伪数组或集合转换为真正的数组 // 类似于 map用法 let p = document.querySelectorAll('p') // 得到p的集合 let pArr = Array.from(p) pArr.forEach(item => { // 属性testContent 原生获取DOM节点文本内容的方法 console.log(item.textContent) /** * 玩法提示:从01~11中任选5个或多个号码,所选号码与开奖号码相同,即中奖540元 * 您选了 0 注,共 0 元 * 今天已售78期,还剩0期 */
}) // 类似于 map用法 console.log(Array.from([1,3,5],item => item*2)) // (3) [2, 6, 10]
- find()/findIndex() // find 只找到第一个满足的成员就停止 console.log([1, 2, 3, 4, 5, 6].find(item => item > 3)) // 4 console.log([1, 2, 3, 4, 5, 6].find(function(item) {return item > 3} )) // 4 // findIndex 返回第一个满足条件的索引 console.log([1, 2, 3, 4, 5, 6].findIndex(item => item > 3)) // 3
-
includes() 是否包含某一元素 console.log([1,2,NaN].includes(1)) // true console.log([1,2,NaN].includes(NaN)) // true console.log([1,2,NaN,undefined].includes(undefined)) // true
数值扩展
// ES6 二进制表示法 0b/0B
console.log(0b0001) // 1
// ES6 八进制表示法 0o/0O
console.log(0o0010) // 8
函数扩展
对象扩展
模块化
- ES6 模块化如何使用,开发环境如何打包?
1.1. 基本语法
-
// utils1.js export default { a: 1 } -
// utils2.js export function fn1() { console.log('fn1') } export function fn2() { console.log('fn2') } -
// index.js // export default -> import xxx import utils1 from './utils1.js' // export ... export ...等 -> import {...} import {fn1, fn2} from './utils2.js' console.log(utils1) fn1() fn2()
1.2. 如何配置/工具
开发环境 - babel
- 电脑有 node 环境,运行 npm init 初始化
- npm install --save-dev babel-core babel-preset-es2015 babel-preset-latest 安装babel的依赖
- 创建 .babelrc 文件(隐藏文件)
- npm install --global babel-cli 界面工具
- babel --version 检测是否安装成功
- 创建 ./src/index.js
开发环境 - webpack
- npm install webpack babel-loader --save-dev 安装webpack插件
- 配置 webpack.config.js
- 配置 package.json 中的 scripts
- 运行 npm start
开发环境 - rollup
- npm init 初始化
- npm i rollup rollup-plugin-node-resolve rollup-plugin-babel babel-plugin-external-helpers babel-preset-latest --save-dev 安装依赖
- 配置 .babelrc
- 配置 rollup.config.js
- 编写代码
- 修改 package.json 的 scripts
- 运行 npm start
- 对比:
- rollup 功能单一,webpack 功能强大
- 参考设计原则 《Linux/Unix设计思想》
- 工具尽量单一、可集成、可扩展
- wangEditor 用的 gulp + rollup
1.3. 关于JS众多模块化标准
- 没有模块化
- AMD 成为标准,require.js (也有CMD)
- 前端打包工具(grunt、gulp、webpack等),使node.js模块化可以被使用
- ES6 出现,统一现在搜友模块化标准
- nodejs积极支持,浏览器尚未统一
- 可以自造lib,但不要自造标准!!!
- 问题解答
2.1. 语法:
import export (注意有无 default)
2.2. 环境:
- babel 编译ES6语法
- 模块化可以使用webpack和rollup
2.3. 扩展:
- 说一下自己对模块化标准统一的期待
Class
- Class和普通构造函数的区别
1.1. Class语法
- JS 构造函数 // 构造函数 function MathHandle(x,y) { this.x = x this.y = y } // 原型扩展 MathHandle.prototype.add = function () { return this.x + this.y } // 实例对象 var m = new MathHandle(1,2) console.log(m.add()) // 3
- Class基本语法
- constructor
- static 静态方法
- getter/setter 属性 class MathHandle { // 函数中定义的变量,立即执行 constructor(x,y) { this.x = x this.y = y } // 原型中定义的方法 add() { return this.x + this.y } // 静态方法 static -> 只能通过类去调用 // 静态属性直接写:MathHandle.type = xxx static say() { console.log('MathHandle') } // 属性:getter/setter -> 获取/设置 get longName() { return 'class-' + this.x } set longName(value) { this.name = value } } const m = new MathHandle(1, 2) console.log(m.longName) // class-1 m.longName = 'hello' console.log(m.longName) // class-hello console.log(m.add()) // hello3 MathHandle.say() // 'MathHandle'
- 语法糖 class MathHandle { // ... } typeof MathHandle // 'function' MathHandle === MathHandle.prototype.constructor // true // 这种语法糖形式,看起来和实际与哪里不一样的东西,个人不太赞同 // 形式上强行模仿 java C#,失去了它的本性和个性 m.proto === MathHandle.prototype // 实例的隐式原型 === 构造函数的显示原型
- 继承 - JS
// 动物
function Animal() {
this.eat = function() {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function() {
coonsole.log('don bark')
}
}
// 低级构造函数的原型赋值为高级构造函数的实例
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
-
原型继承 优点:完美继承了方法 缺点:无法完美继承属性
-
先更改子类的原型prototype指向一个new 父类() 对象 子类.prototype = new 父类
-
再给子类的原型设置一个constructor属性指向子类 子类.prototype.constructor = 子类
-
代码 // Person function Person(name, age, gender) { this.name = name this.age = age this.gender = gender } // 父类原型中添加方法 Person.prototype.sayHi = function () { console.log(this.name + 'hello') } Person.prototype.eat = function () { console.log(this.age + 'eat') } Person.prototype.play = function () { console.log(this.gender + 'play') } // Student function Student(studentId) { this.studentId = studentId } // 子类的原型prototype指向父类的一个实例对象 Student.prototype = new Person() // 添加一个constructor属性 Student.prototype.constructor = Student
var stu1 = new Student()
-
-
借用继承 优点:完美继承属性 缺点:无法继承方法
- 在子类中,通过call方法调用父类,并改变父类中的this指向子类
- 代码 // Person function Person(name, age, gender) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHi = function () {console.log(this.name + 'hello')} // Student function Student (name, age, gender, studentId) { Person.call(this, name, age, gender) this.studentId = studentId } var stu2 = new Student('xiaos', 10, '男', 1086)
-
组合继承 原型继承 + 借用继承 // Person function Person(name, age, gender) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHi = function() {console.log(this.name + ' hello!')} // Student function Student(name, age, gender, studentId) { this.studentId = studentId // 借用继承 Person.call(this, name, age, gender) } // 原型继承 Student.prototype = new Person() Student.prototype.constructor = Student
var stu3 = new Student('xiao', 10, '男', 10001) stu3.sayHi() // xiao hello! console.log(stu3.name) // xiao
-
- 继承 - Class
- super 传递父类中的参数,一般写在第一行,必须写在this之前
class Animal {
constructor(name='animal') {
this.name = name
}
eat() {
console.log(
${this.name} eat) } } // 类似于 Dog.prototype = new Animal() class Dog extends Animal { constructor(name='dog') { // 通过super传递参数,一般放在第一行,必须写在this之前 super(name) // 先去执行Animal的构造器 this.name = name } say() { console.log(${this.name} say) } } const dog = new Dog('哈士奇') dog.say() dog.eat()
- super 传递父类中的参数,一般写在第一行,必须写在this之前
class Animal {
constructor(name='animal') {
this.name = name
}
eat() {
console.log(
- 问题解答
- Class 在语法上更加贴近面向对象的写法
- Class 实现继承更加易读、易理解
- 更易于写java等后端语言的使用
- 本质还是语法糖,使用prototype
Promise 的基本使用和原理
总结 ES6 其它常用功能
- 新增功能
- let/const
- 块级作用域
- 多行字符串/模板变量
- 解构赋值
- 函数默认参数
- 箭头函数
- 旧功能新写法
- class
- promise
- 模块化
原型的应用
-
zepto jQuery 中如何使用原型?
-
原型的实际应用
-
原型如何体现它的扩展性
原型的实际应用
-
zepto
jQuery 1
jQuery 2
jQuery 3
<div id="div1"> <p>jQuery 4</p> </div> --------------------------------- <script type="text/javascript"> // 自调用函数 (function (window) { var zepto = {} // 构造函数 function Z(dom, selector) { var i, len = dom ? dom.length : 0 for (i = 0; i < len; ++i) { this[i] = dom[i] } this.length = len this.selector = selector || '' } zepto.Z = function (dom, selector) { return new Z(dom, selector) } zepto.init = function (selector) { var slice = Array.prototype.slice // 将伪数组转换为数组 var dom = slice.call(document.querySelectorAll(selector)) console.log(slice, document.querySelectorAll(selector), dom) return zepto.Z(dom, selector) } var $ = function (selector) { return zepto.init(selector) } // 原型 $.fn = { constructor: zepto.Z, css: function (key, value) { console.log('css') }, html: function (value) { console.log('html') } } // 指定构造函数的原型 zepto.Z.prototype = Z.prototype = $.fn window.$ = $ // console.log($()) // Z {length: 0, selector: ""} 实例对象 })(window) var $p = $('p') $p.css('color', 'red') console.log($p.html()) var $div1 = $('#div1') $div1.css('color', 'blue') console.log($div1.html()) </script> -
jQuery
- 遇到的问题
- DOM操作是昂贵的,js 运行效率高
- 尽量减少 DOM 操作,而不是推倒重来
- 项目越复杂,影响就越严重
- vdom 可以解决这个问题
1.2. vdom 如何应用,核心 API 是什么
-
介绍 snabbdom
- h() 生成虚拟node
- patch(container, vnode) 虚拟节点生成/比较真实节点
-
重做之前的 demo
-
两者的区别
- 数据和视图的分离 !!!
- jQuery 中数据和视图混在一起
- vue 中的数据在 data中
- vue 中的模板先独立写好
- 以数据驱动视图
- jQuery 中不存在数据驱动视图
- vue 中修改 data中的数据就会改变视图
- 数据和视图的分离 !!!
-
问题解答
- 数据和视图的分离,解耦(开放封闭原则)
- 以数据驱动视图,只关心数据变化,DOM操作被封装
- 说一下对 MVVM 的理解
- mvc
- model 数据源
- view 视图
- controller 控制器
- 流程:用户 -> view -> controller -> model -> view
- mvvm
- v -> view 视图、模板
- m -> model 模型、数据data
- vm -> new Vue() 连接 model和view
- 数据data通过 -> vm -> 完成对视图view的绑定
- 视图通过 -> vm中的事件绑定 -> 修改数据data
- 关于 ViewModel
- mvvm 不算一种创新
- 其中的viewmodel确实是一种创新
- 真正结合前端场景应用的创建
- 问题解答
- MVVM - Model、View、ViewModel
- 三者之间的联系,以及对应的各段代码
- ViewModel 的理解。联系 View 和 Model
- MVVM框架的三要素
- 再次分析 demo
- 三要素总结
- 响应式:vue如何监听到 data中每个属性的变化
- 模板引擎:vue 的模板引擎如何被解析,指令如何处理
- 渲染:vue 的模板如何被渲染成 html,以及渲染过程
4.1. vue中的响应式
-
什么是响应式
- 修改 data 属性后,vue立刻监听到并修改视图
- data 属性被代理到 vm 上
-
Object.defineProperty(实现响应式的核心函数)
-
模拟响应式 const vm = {} const data = { name: '222', age: 20 } let key, value for (key in data) { (function (key) { Object.defineProperty(vm, key, { get() { return data[key] }, set(newVal) { data[key] = newVal } }) })(key) }
-
问题解答
- 关键是理解 Object.defineProperty
- 将 data 的属性代理到 vm 上
4.2. vue 中如何解析模板
-
模板是 what
- 本质:字符串
- 有逻辑,如 v-if v-for 等
- 与 html 格式很像,但有很大区别,静态无逻辑
- 最终还是要转换为 html 来显示
- 模板最终必须转化为 JS 代码,因为:
- 有逻辑(v-if v-for),必须要用 JS 才能实现
- 转换为 html 渲染页面,必须用 JS 才能实现
- 因此,模板最终要转换成一个 JS 函数(render 函数)
-
render 函数
-
with 的用法 const obj = { name: 'zs', age: 20, getAddress() { console.log('beijing') } } // 使用 with function fn() { with(obj) { console.log(name) console.log(age) getAddress() } } fn()
-
render 函数详解
// vm === this with(this) { return _c( 'div', { attrs: {"id": "app"} }, [ _c("p", [_v(_s(price))]) ] ) }{{price}}
- 模板中所有的信息都包含在 render 函数中
- this === vm
- price 即 this.price 即 vm.price,即 data 中的 price
- _c 即 this.c 即 vm._c
-
从哪可以看到render函数
- 在 vue源码中 code.render 打印到控制台
-
复杂一点的例子,render函数是什么样的,如下
-
v-if v-for v-on 的处理
vsubmit- {{item}}
{ with (this) { 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.target.composing) return; title = $event.target.value } } }), // 换行,空字符串 _v(" "), _c( 'button', { on: { "click": add } }, [ _v("vsubmit") ]) ]), _v(" "), _c( 'ul', _l( (list), function (item) { return _c('li', { key: "item" }, [_v(_s(item))]) }) ) ] ) } }
-
-
render 函数与 vdom
- vm._c 就相当于 snabbdom 中的 h函数
- render 函数执行后,返回的是 vnode
- updateComponent 中实现了 vdom 的 patch
- 页面首次渲染执行 updateComponent
- data 中每次修改属性,执行 updataComponent
-
问题解答
- 模板:字符串,有逻辑,嵌入 JS 变量...
- 模板必须转换为 js 代码(有逻辑、渲染html、JS变量)
- render 函数的样子(with()) == h()
- render 函数执行返回 vnode
- updateComponent 更新渲染
4.3. vue 的整个实现流程
- 第一步:解析模板成 render函数
- 写模板
- 编译打包 -> render函数 -> vnode
- with 的用法
- 模板中的所有信息都被 render 函数包含
- 模板中用到的data中的属性,都变为 JS 变量
- 模板中的 v-model v-for v-on 都变成了 JS 逻辑
- render函数返回 vnode(开始和虚拟DOM结合)
- 第二步:响应式开始监听
- Object.defineProperty
- 将 data 中的属性代理到 vm 上
- 第三步:首次渲染,显示页面,且绑定依赖(get/set)
- 初次渲染,执行 updateComponent,执行 vm._render()
- 执行 render函数,会访问到vm.list 和 vm.title
- 会被响应式的 get() 监听到
- 为什么要监听 get,直接监听 set 不行吗
- data 中有很多属性,有些被用到,有些可能不被用到
- 被用到的会走 get,不被用到的不会走 get
- 未走 get 的属性,set 时我们也无需关心
- 避免不必要的重复渲染
- 执行 updateComponent,会走到 vdom 的 patch 方法
- patch 将 vnode 渲染成 DOM,初次渲染完成
- 第四步:data属性变化,触发 re-render
- 修改属性,被响应式的 set 监听到
- set 中执行 updateComponent
- updateComponent 重新执行 vm._render()
- 生成的 vnode 和 preVnode,通过 patch 进行对比
- 渲染到 html 中
总结
- 使用jQuery和使用框架的区别
- 对 MVVM 的理解
- vue 中如何实现响应式
- vue 中如何解析模板
- vue 的整体实现流程
组件化和React
- 是否做过 React 开发?
- React 以及组件化的一些核心概念
- 实现流程
- 题目
- 说一下对组件化的理解
- JSX 的本质
- JSX 和 vdom 的关系
- 说一下 setState 的过程
- 阐述自己对 React 和 vue 的认识
- 说一下对组件化的理解
- 组件的封装
- 视图
- 数据
- 变化逻辑(数据驱动视图变化)
- 组件的复用
- props 传递数据
- 复用
- 总结
- 组件的封装:封装视图、数据、逻辑
- 组件的复用:props传递、复用
- JSX 的本质是什么
3.1. JSX 语法
- html 形式
- 引入 JS 变量和表达式
- if ... else ...
- 循环
-
{list.map((item, index) => {
return
- {item} })}
- style 和 className
<p style={{fontSize: '36px', color: 'blue'}}>this is JSX
- 事件
- JSX 语法根本无法被浏览器解析
3.2. JSX 解析成 JS
语法:
React.createElement('div', {id: 'div1'}, child1, child2, ...)
React.createElement('div', {id: 'div1'}, [child1, dhild2, ...])
render() {
const list = this.props.list
return React.createElement(
'ul',
null,
list.map((item, index) => { // [...]
return React.createElement(
'li',
{key: index},
item
)
})
)
}
- JSX 其实是语法糖
- 开发环境会将 JSX 编译成 JS 代码
- JSX 的写法大大降低了学习成本和编码工作量
- 同时,JSX 也会增加 debug 成本
3.3. JSX 独立的标准(可以被用到其它地方)
- JSX 是 React 的引入的,但不是 React 独有的
- React 已经将它作为一个独立标准开放,其它项目也可使用
- React.createElement 是可以自定义修改的 -> /* @jsx h */
- 说明:本身功能已完备;和其它标准兼容和扩展性没问题
3.4. 问题解答
- JSX 语法(标签、JS 表达式、判断、循环、事件绑定)
- JSX 是语法糖,需被解析成 JS 才能运行
- JSX 是独立的标准,可被其它项目使用
- JSX 和 vdom 的关系
vdom 是 React 引入并且推广到全世界, 其在React中非常重要
JSX -> JS代码 -> vnode
4.1. 分析:为何需要 vdom
- vdom 是 React 初次推广开来的,结合 JSX
- JSX 就是模板,最终要渲染成 html(类似于 vue 的模板)
- 初次渲染 + 修改 state 后的 re-render
- 正好符合 vdom 的应用场景
4.2. React.createElement() 和 h()
- 都会返回 vnode
4.3. 何时 patch?
- 初次渲染 - ReactDOM.render(, container)
- 会触发 patch(container, vnode)
- re-render - setState({})
- 会触发 patch(vnode, newVnode)
4.4. 自定义组件的解析
return React.createElement('div', null,
React.createElement(Input, {addTitle: this.addTitle.bind(this)}),
React.createElement(List, {data: this.state.list}),
React.createElement('p', null, 'this is demo')
)
// React.createElement(List, {data: this.state.list}), 类似以下代码
const list = new List({data: this.state.list})
const vnode = list.render()
- 'div' - 直接渲染成 即可,vdom可以做到
- Input 和 List,是自定义组件 class,vdom 默认不认识
- 因此,Input 和 List 定义时必须声明 render 函数
- 根据 props 初始化实例,然后执行实例的 render 函数
- render 函数返回的还是 vnode 对象
- 问题解答
- 为何需要 vdom:JSX 需要渲染成 html,数据驱动视图
- JS 在数据驱动视图场景下,需要 vdom(中间层)渲染 html
- React.createElement 和 h 一样,都生成 vnode
- 何时 patch:ReactDOM.render(...) 和 setState
- 自定义组件的解析:初始化实例,然后执行 render
- 说一下 React setState 的过程
- setState 是异步操作
- vue 修改属性也是异步
- setState 的过程
6.1. setState 为何需要异步
-
可能会一次执行多次 setState
-
你无法规定、限制用户如何使用 setState
-
没必要每次 setState 都需要重新渲染,考虑性能
-
即便是每次重新渲染,用户也看不到中间效果
-
只看到最后的结果即可
addTitle(title) { const list = this.state.list this.setState({ list: list.concat(title) }) this.setState({ list: list.concat(title + 1) }) this.setState({ list: list.concat(title + 2) }) }
6.2. vue 修改属性也是异步
- 效果、原因和 setState 一样
- 对比记忆,印象深刻
- 复习一下 vue 的渲染流程
- 解析模板成 render 函数
- 响应式开始监听
- 首次渲染,显示页面,且绑定依赖
- data 属性变化,触发 re-render
- 修改属性,被响应式的 set 监听到
- set 中执行 updateComponent(异步执行)
- updateComponent 重新执行 vm._render()
- 生成的 vnode 和 preVnode,通过 patch 进行对比
- 渲染到 html 中
6.3. setState 的过程
-
每个组件实例,都有 renderComponent (在React.Component类中)方法
-
执行 renderComponent 会重新执行实例的 render
-
render 函数返回 newVnode,然后拿到 preVnode
-
执行 patch(preVnode, newVnode)
this.setState({ ///// }, () => { // 回调,此时setState异步已执行完成 })
6.4. 问题解答
- setState 的异步:效果、原因
- vue 修改属性也是异步:效果、原因
- setState 的过程:最终走到 patch(preVnode, newVnode)
- React vs Vue
-
前言
- 技术选型没有绝对的对与错
- 技术选型要考虑的因素非常多
- 作为面试者,要有自己的主见
- 和面试官观点不一致没关系,只要说出理由
-
两者的本质区别
- vue - 本质是 MVVM 框架,由 MVC 发展而来
- React - 本质是前端组件化框架,由后端组件化发展而来(smart)
- 但这并不妨碍他们都能实现相同的功能
-
模板的区别
- vue - 使用模板(angular提出)
- React - 使用 JSX
- 模板语法上,倾向于 JSX
- 模板分离上,倾向于 vue
-
组件化的区别
- React 本身就是组件化,没有组件化就不是 React
- vue 也支持组件化,不过是在 MVVM 上的扩展
- vue 组件化的文档,洋洋洒洒很多(侧面反映)
- 对于组件化,更倾向于 React,做的更彻底清晰
-
两者共同点
- 支持组件化
- 数据驱动视图
-
总结答案
- 国内使用,首推 vue。文档易读、易学、社区够大
- 团队水平较高,推荐使用 React。组件化和 JSX
- 总结
- 说一下对组件化的理解
- 封装
- 复用
- 结合项目
- JSX 的本质
- JSX 语法(标签、JS表达式、判断、循环、事件绑定)
- JSX 是语法糖,需被解析成 JS 才能运行
- JSX 是独立的标准,可被其它项目使用
- JSX 和 vdom 的关系
- 为何需要 vdom:JSX 需要渲染成 html,还有 re-render
- React.createElement 和 h,都生成 vnode
- 何时 patch:ReactDOM.render(...) 和 setState
- 自定义组件的解析:初始化实例,然后执行 render
- 说一下 setState 的过程
- setState 的异步:效果、原因
- vue 修改属性也是异步:效果、原因
- setState 的过程:最终走到 patch(preVnode, newVnode)
- 阐述自己对 React 和 vue 的认识(技术选型)
hybrid
- 题目
- hybrid是什么,为何使用hybrid
- 介绍一下hybrid更新和上线流程
- hybrid和h5的主要区别
- 前端JS和客户端如何通信
- hybrid是什么,为何会用hybrid
2.1. hybrid文字解释
- hybrid 即混合,即前端和客户端的混合开发
- 需前端开发人员和客户端开发人员配合完成
- 在某些环节(更新/升级)可能涉及到server端
- 不要以为自己是前端就可以不理会客户端的知识
2.2. 存在价值,为何会用 hybrid
- 可以快速迭代更新关键(无需app审核,纯前端代码,没有访问位置相机等权限)
- 体验流畅(和NA的体验基本类似)
- 减少开发和沟通成本,双端共用一套代码
2.3. webview
浏览器的内核,可以承载html页面并显示
- app的一个组件(app可以有webview,也可以没有)
- 用于加载h5页面,即一个小型的浏览器内核
2.4. file:// 协议
- 其实在一开始接触html开发,就已经使用了file协议
- 只不过你当时没有协议、标准等这些概念
- 再次强调协议、标准的重要性!!!
- 两者区别
- file 协议:本地文件,快
- http(s) 协议:网络加载,慢
- file:// 协议 后边跟的是文件的绝对路径
2.5. hybrid 实现流程
- 不是所有的场景都适合使用hybrid
- 使用NA:体验要求极致,变化不频繁(如头条首页)
- 使用hybrid:体验要求高,变化频繁(如头条的新闻详情页)
- 使用h5:体验无要求,不常用(如举报、反馈等)
- 前端做好静态页面(html css js),将文件交给客户端
- 客户端拿到前端的静态页面,以文件的形式存储在app中
- 客户端在一个 webview 中
- 使用 file 协议加载静态页面
2.6. 问题解答
- hybrid 是客户端和前端的混合开发
- hybrid 存在的核心意义在于快速迭代,无需审核
- hybrid 实现流程(图),以及 webview 和 file 协议
2.7. hybrid 更新上线流程
- 回顾hybrid 实现流程
- 思考(目的、实现途径)
- 替换每个客户端的静态文件
- 只能客户端来做(客户端是我们开发的)
- 客户端去server下载最新的静态文件
- 我们维护server的静态文件
- 更新流程
- 分版本,有版本号,如:时间戳 201903211015
- 将静态文件压缩成 zip 包,上传到服务端
- 客户端每次启动,都去服务端检查版本号
- 如果服务端版本号大于客户端版本号,就去下载最新的zip包
- 下载完之后解压包,然后将现有文件覆盖,完成更新
- 问题解答
- 掌握流程图
- 要点1:服务端的版本和 zip 包维护
- 要点2:更新 zip 包之前,先对比版本号
- 要点3:zip 包下载解压和覆盖
- hybrid 和 h5 的区别
3.1. 优点
- 体验更好,跟NA体验基本一致
- 可快速迭代,无需 app 审核(关键)
3.2. 缺点
- 开发成本高。联调、测试、查 bug 都比较麻烦
- 运维成本高。参考此前的更新上线流程
3.3. 场景
- hybrid:产品的稳定功能,体验要求高,迭代频繁
- h5:单次的运营活动(如 xx红包)或不常用功能
- 前端和客户端通信
4.1. hybrid 中前端如何获取新闻内容
- 不能使用 ajax获取。第一 跨域,第二 速度慢
- 客户端获取新闻内容,然后 JS 通讯拿到内容,再渲染
4.2. JS 和 客户端通讯的基本形式
- JS 访问客户端能力,传递参数和回调函数
- 客户端通过回调函数返回内容
4.3. schema 协议简介和使用
-
scheme 协议 - 前端和客户端通讯的约定
<script type="text/javascript"> function invokeScan() { window['_invole_scan_callback'] = function (result) { console.log(result) } let iframe = document.createElement('iframe') iframe.style.display = 'none' // schema 传递参数和回调 // iframe.src = 'weixin://dl/scan?k1=v1&k2=v2&callback=_invoke_scan_callback' const body = document.body body.appendChild(iframe) setTimeout(() => { body.removeChild(iframe) iframe = null }) } document.getElementById('btn').addEventListener('click', () => { invokeScan() }) </script>
4.4. schema 使用的封装
-
schema 使用的封装 window.invoke.share({title: 'xxx', content: 'xxx'}, (result) => { if(result.err === 0) { console.log('成功') }else { console.log('失败') } })
-
schema 封装-内部实现 document.getElementById('btn').addEventListener('click', () => { window.invoke.share(data, callback) }) // --- function invokeShare(data, callback) { _invoke('share', data, callback) } function invokeLogin(data, callback) { _invoke('login', data, callback) } function invokeScan(data, callback) { _invlke('scan', data, callback) }
window.invoke = { share: invokeShare, login: invokeLogin, scan: invokeScan } function _invoke(action, data, callback) { let schema = 'myapp://utils' schema += '/' + action + '?' let url = '' for (key in data) { if (data.hasOwnProperty(key)) { url += key + '=' + data[key] + '&' } } schema += url.substr(0, url.length) // 处理 callback let callbackName = '' if (typeof callback === 'string') { callbackName = callback } else { callbackName = action + Date.now() window[callbackName] = callback } schema += '&callback=' + callbackName // iframe 中调用 schema 省略 n行 let iframe = document.createElement('iframe') iframe.style.display = 'none' // schema 传递参数和回调 iframe.src = schema const body = document.body body.appendChild(iframe) setTimeout(() => { body.removeChild(iframe) iframe = null }) }
4.5 内置上线
- 将以上封装的代码打包,叫做 invoke.js,内置到客户端
- 客户端每次启动 webview,都默认执行 invoke.js
- 本地加载,免去网络加载时间,更快 本地加载,没有网络请求,黑客看不到 schema 协议,更安全
4.6. 问题解答
- 通讯的基本形式:调用能力,传递参数,监听回调
- 对 schema 协议的理解和使用(自己规定前端和客户端如何通信传参与http相似)
- 调用 schema 代码的封装
- 内置上线的好处:更快、更安全
- 总结
5.1. hybrid 是什么,为何用 hybrid
- 客户端和前端的混合开发
- hybrid 存在的核心意义在于快速迭代,无需审核
- hybrid 实现流程(图),以及 webview 和 file 协议
5.2. hybrid 更新上线的流程
- 掌握流程图
- 要点1:服务端的版本和zip包的维护
- 要点2:更新 zip 包之前,先对比版本号
- 要点3:zip 下载解压和覆盖
5.3. hybrid 和 h5 的区别
- 优点:体验好,可快速迭代
- 缺点:开发成本高,运维成本高
- 适用场景:hybrid 适用产品型,h5 适用运营型
5.4. 前端和客户端通信
- 通讯的基本形式:调用能力,传递参数,监听回调
- 对 schema 协议的理解和使用(类似于http)
- 调用 schema 代码的封装
- 内置上线:更快、更安全