面试总结

111 阅读23分钟

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(abcI 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

函数扩展

对象扩展

模块化

  1. 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
  • 对比:
    1. rollup 功能单一,webpack 功能强大
    2. 参考设计原则 《Linux/Unix设计思想》
    3. 工具尽量单一、可集成、可扩展
    4. wangEditor 用的 gulp + rollup

1.3. 关于JS众多模块化标准

  • 没有模块化
  • AMD 成为标准,require.js (也有CMD)
  • 前端打包工具(grunt、gulp、webpack等),使node.js模块化可以被使用
  • ES6 出现,统一现在搜友模块化标准
  • nodejs积极支持,浏览器尚未统一
  • 可以自造lib,但不要自造标准!!!
  1. 问题解答

2.1. 语法:

import export (注意有无 default)

2.2. 环境:

  • babel 编译ES6语法
  • 模块化可以使用webpack和rollup

2.3. 扩展:

  • 说一下自己对模块化标准统一的期待

Class

  1. 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基本语法
    1. constructor
    2. static 静态方法
    3. 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()
    1. 原型继承 优点:完美继承了方法 缺点:无法完美继承属性

      • 先更改子类的原型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()

    2. 借用继承 优点:完美继承属性 缺点:无法继承方法

      • 在子类中,通过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)
    3. 组合继承 原型继承 + 借用继承 // 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
    1. 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()
  1. 问题解答
  • Class 在语法上更加贴近面向对象的写法
  • Class 实现继承更加易读、易理解
  • 更易于写java等后端语言的使用
  • 本质还是语法糖,使用prototype

Promise 的基本使用和原理

总结 ES6 其它常用功能

  1. 新增功能
  • let/const
  • 块级作用域
  • 多行字符串/模板变量
  • 解构赋值
  • 函数默认参数
  • 箭头函数
  1. 旧功能新写法
  • class
  • promise
  • 模块化

原型的应用

  1. zepto jQuery 中如何使用原型?

  2. 原型的实际应用

  3. 原型如何体现它的扩展性

原型的实际应用

  • 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

  • 遇到的问题
    1. DOM操作是昂贵的,js 运行效率高
    2. 尽量减少 DOM 操作,而不是推倒重来
    3. 项目越复杂,影响就越严重
    4. vdom 可以解决这个问题

1.2. vdom 如何应用,核心 API 是什么

  • 介绍 snabbdom

    1. h() 生成虚拟node
    2. patch(container, vnode) 虚拟节点生成/比较真实节点
  • 重做之前的 demo

  • 两者的区别

    1. 数据和视图的分离 !!!
      • jQuery 中数据和视图混在一起
      • vue 中的数据在 data中
      • vue 中的模板先独立写好
    2. 以数据驱动视图
      • jQuery 中不存在数据驱动视图
      • vue 中修改 data中的数据就会改变视图
  • 问题解答

    1. 数据和视图的分离,解耦(开放封闭原则)
    2. 以数据驱动视图,只关心数据变化,DOM操作被封装
  1. 说一下对 MVVM 的理解
  • mvc
    1. model 数据源
    2. view 视图
    3. controller 控制器
    4. 流程:用户 -> view -> controller -> model -> view
  • mvvm
    1. v -> view 视图、模板
    2. m -> model 模型、数据data
    3. vm -> new Vue() 连接 model和view
    4. 数据data通过 -> vm -> 完成对视图view的绑定
    5. 视图通过 -> vm中的事件绑定 -> 修改数据data
  • 关于 ViewModel
    1. mvvm 不算一种创新
    2. 其中的viewmodel确实是一种创新
    3. 真正结合前端场景应用的创建
  • 问题解答
    1. MVVM - Model、View、ViewModel
    2. 三者之间的联系,以及对应的各段代码
    3. ViewModel 的理解。联系 View 和 Model
  1. MVVM框架的三要素
  • 再次分析 demo
  • 三要素总结
    1. 响应式:vue如何监听到 data中每个属性的变化
    2. 模板引擎:vue 的模板引擎如何被解析,指令如何处理
    3. 渲染:vue 的模板如何被渲染成 html,以及渲染过程

4.1. vue中的响应式

  • 什么是响应式

    1. 修改 data 属性后,vue立刻监听到并修改视图
    2. 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) }

  • 问题解答

    1. 关键是理解 Object.defineProperty
    2. 将 data 的属性代理到 vm 上

4.2. vue 中如何解析模板

  • 模板是 what

    1. 本质:字符串
    2. 有逻辑,如 v-if v-for 等
    3. 与 html 格式很像,但有很大区别,静态无逻辑
    4. 最终还是要转换为 html 来显示
    5. 模板最终必须转化为 JS 代码,因为:
      • 有逻辑(v-if v-for),必须要用 JS 才能实现
      • 转换为 html 渲染页面,必须用 JS 才能实现
      • 因此,模板最终要转换成一个 JS 函数(render 函数)
  • render 函数

    1. with 的用法 const obj = { name: 'zs', age: 20, getAddress() { console.log('beijing') } } // 使用 with function fn() { with(obj) { console.log(name) console.log(age) getAddress() } } fn()

    2. render 函数详解

      {{price}}

      // vm === this with(this) { return _c( 'div', { attrs: {"id": "app"} }, [ _c("p", [_v(_s(price))]) ] ) }

      • 模板中所有的信息都包含在 render 函数中
      • this === vm
      • price 即 this.price 即 vm.price,即 data 中的 price
      • _c 即 this.c 即 vm._c
    3. 从哪可以看到render函数

      • 在 vue源码中 code.render 打印到控制台
    4. 复杂一点的例子,render函数是什么样的,如下

    5. 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) {
                     if (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

    1. vm._c 就相当于 snabbdom 中的 h函数
    2. render 函数执行后,返回的是 vnode
    3. updateComponent 中实现了 vdom 的 patch
    4. 页面首次渲染执行 updateComponent
    5. data 中每次修改属性,执行 updataComponent
  • 问题解答

    1. 模板:字符串,有逻辑,嵌入 JS 变量...
    2. 模板必须转换为 js 代码(有逻辑、渲染html、JS变量)
    3. render 函数的样子(with()) == h()
    4. render 函数执行返回 vnode
    5. updateComponent 更新渲染

4.3. vue 的整个实现流程

  • 第一步:解析模板成 render函数
    1. 写模板
    2. 编译打包 -> render函数 -> vnode
    3. with 的用法
    4. 模板中的所有信息都被 render 函数包含
    5. 模板中用到的data中的属性,都变为 JS 变量
    6. 模板中的 v-model v-for v-on 都变成了 JS 逻辑
    7. render函数返回 vnode(开始和虚拟DOM结合)
  • 第二步:响应式开始监听
    1. Object.defineProperty
    2. 将 data 中的属性代理到 vm 上
  • 第三步:首次渲染,显示页面,且绑定依赖(get/set)
    1. 初次渲染,执行 updateComponent,执行 vm._render()
    2. 执行 render函数,会访问到vm.list 和 vm.title
    3. 会被响应式的 get() 监听到
      • 为什么要监听 get,直接监听 set 不行吗
      • data 中有很多属性,有些被用到,有些可能不被用到
      • 被用到的会走 get,不被用到的不会走 get
      • 未走 get 的属性,set 时我们也无需关心
      • 避免不必要的重复渲染
    4. 执行 updateComponent,会走到 vdom 的 patch 方法
    5. patch 将 vnode 渲染成 DOM,初次渲染完成
  • 第四步:data属性变化,触发 re-render
    1. 修改属性,被响应式的 set 监听到
    2. set 中执行 updateComponent
    3. updateComponent 重新执行 vm._render()
    4. 生成的 vnode 和 preVnode,通过 patch 进行对比
    5. 渲染到 html 中

总结

  • 使用jQuery和使用框架的区别
  • 对 MVVM 的理解
  • vue 中如何实现响应式
  • vue 中如何解析模板
  • vue 的整体实现流程

组件化和React

  • 是否做过 React 开发?
  • React 以及组件化的一些核心概念
  • 实现流程
  1. 题目
  • 说一下对组件化的理解
  • JSX 的本质
  • JSX 和 vdom 的关系
  • 说一下 setState 的过程
  • 阐述自己对 React 和 vue 的认识
  1. 说一下对组件化的理解
  • 组件的封装
    1. 视图
    2. 数据
    3. 变化逻辑(数据驱动视图变化)
  • 组件的复用
    1. props 传递数据
    2. 复用
  • 总结
    1. 组件的封装:封装视图、数据、逻辑
    2. 组件的复用:props传递、复用
  1. 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 是独立的标准,可被其它项目使用
  1. 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 对象
  1. 问题解答
  • 为何需要 vdom:JSX 需要渲染成 html,数据驱动视图
  • JS 在数据驱动视图场景下,需要 vdom(中间层)渲染 html
  • React.createElement 和 h 一样,都生成 vnode
  • 何时 patch:ReactDOM.render(...) 和 setState
  • 自定义组件的解析:初始化实例,然后执行 render
  1. 说一下 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 的渲染流程
    1. 解析模板成 render 函数
    2. 响应式开始监听
    3. 首次渲染,显示页面,且绑定依赖
    4. 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)
  1. React vs Vue
  • 前言

    1. 技术选型没有绝对的对与错
    2. 技术选型要考虑的因素非常多
    3. 作为面试者,要有自己的主见
    4. 和面试官观点不一致没关系,只要说出理由
  • 两者的本质区别

    1. vue - 本质是 MVVM 框架,由 MVC 发展而来
    2. React - 本质是前端组件化框架,由后端组件化发展而来(smart)
    3. 但这并不妨碍他们都能实现相同的功能
  • 模板的区别

    1. vue - 使用模板(angular提出)
    2. React - 使用 JSX
    3. 模板语法上,倾向于 JSX
    4. 模板分离上,倾向于 vue
  • 组件化的区别

    1. React 本身就是组件化,没有组件化就不是 React
    2. vue 也支持组件化,不过是在 MVVM 上的扩展
    3. vue 组件化的文档,洋洋洒洒很多(侧面反映)
    4. 对于组件化,更倾向于 React,做的更彻底清晰
  • 两者共同点

    1. 支持组件化
    2. 数据驱动视图
  • 总结答案

    1. 国内使用,首推 vue。文档易读、易学、社区够大
    2. 团队水平较高,推荐使用 React。组件化和 JSX
  1. 总结
  • 说一下对组件化的理解
    1. 封装
    2. 复用
    3. 结合项目
  • JSX 的本质
    1. JSX 语法(标签、JS表达式、判断、循环、事件绑定)
    2. JSX 是语法糖,需被解析成 JS 才能运行
    3. JSX 是独立的标准,可被其它项目使用
  • JSX 和 vdom 的关系
    1. 为何需要 vdom:JSX 需要渲染成 html,还有 re-render
    2. React.createElement 和 h,都生成 vnode
    3. 何时 patch:ReactDOM.render(...) 和 setState
    4. 自定义组件的解析:初始化实例,然后执行 render
  • 说一下 setState 的过程
    1. setState 的异步:效果、原因
    2. vue 修改属性也是异步:效果、原因
    3. setState 的过程:最终走到 patch(preVnode, newVnode)
  • 阐述自己对 React 和 vue 的认识(技术选型)

hybrid

  1. 题目
  • hybrid是什么,为何使用hybrid
  • 介绍一下hybrid更新和上线流程
  • hybrid和h5的主要区别
  • 前端JS和客户端如何通信
  1. 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协议
  • 只不过你当时没有协议、标准等这些概念
  • 再次强调协议、标准的重要性!!!
  • 两者区别
    1. file 协议:本地文件,快
    2. http(s) 协议:网络加载,慢
  • file:// 协议 后边跟的是文件的绝对路径

2.5. hybrid 实现流程

  • 不是所有的场景都适合使用hybrid
  • 使用NA:体验要求极致,变化不频繁(如头条首页)
  • 使用hybrid:体验要求高,变化频繁(如头条的新闻详情页)
  • 使用h5:体验无要求,不常用(如举报、反馈等)
  1. 前端做好静态页面(html css js),将文件交给客户端
  2. 客户端拿到前端的静态页面,以文件的形式存储在app中
  3. 客户端在一个 webview 中
  4. 使用 file 协议加载静态页面

2.6. 问题解答

  • hybrid 是客户端和前端的混合开发
  • hybrid 存在的核心意义在于快速迭代,无需审核
  • hybrid 实现流程(图),以及 webview 和 file 协议

2.7. hybrid 更新上线流程

  • 回顾hybrid 实现流程
  • 思考(目的、实现途径)
    1. 替换每个客户端的静态文件
    2. 只能客户端来做(客户端是我们开发的)
    3. 客户端去server下载最新的静态文件
    4. 我们维护server的静态文件
  • 更新流程
    1. 分版本,有版本号,如:时间戳 201903211015
    2. 将静态文件压缩成 zip 包,上传到服务端
    3. 客户端每次启动,都去服务端检查版本号
    4. 如果服务端版本号大于客户端版本号,就去下载最新的zip包
    5. 下载完之后解压包,然后将现有文件覆盖,完成更新
  • 问题解答
    1. 掌握流程图
    2. 要点1:服务端的版本和 zip 包维护
    3. 要点2:更新 zip 包之前,先对比版本号
    4. 要点3:zip 包下载解压和覆盖
  1. hybrid 和 h5 的区别

3.1. 优点

  • 体验更好,跟NA体验基本一致
  • 可快速迭代,无需 app 审核(关键)

3.2. 缺点

  • 开发成本高。联调、测试、查 bug 都比较麻烦
  • 运维成本高。参考此前的更新上线流程

3.3. 场景

  • hybrid:产品的稳定功能,体验要求高,迭代频繁
  • h5:单次的运营活动(如 xx红包)或不常用功能
  1. 前端和客户端通信

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 代码的封装
  • 内置上线的好处:更快、更安全
  1. 总结

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 代码的封装
  • 内置上线:更快、更安全