[关闭]
@Bios 2019-06-28T06:51:02.000000Z 字数 27067 阅读 932

JS从初级往高级走

js


这是一个系列记录,也有自身的理解。只为了提高自己,坚持写完

分为8个大的部分:

ES6

现在基本上开发中都在使用ES6,浏览器环境支持不好,可以用babel插件来解决。

采用‘二八定律’,主要涉及ES6常用且重要的部分。

问题:

ES6模块化如何使用,开发环境如何打包

模块化的基本语法

  1. // util1.js
  2. export default {
  3. a : 100
  4. }
  5. const str = 'hello';
  6. export default str;
  7. // export default const str = 'hello'; X
  1. // util2.js
  2. export function fn1() {
  3. console.log('fn1');
  4. }
  5. export function fn2() {
  6. console.log('fn2');
  7. }
  8. export const fn3 = ()=> {
  9. console.log('fn3');
  10. }
  1. // index.js
  2. import str from './util1.js';
  3. import {fn1 , fn2} from './util2.js';
  4. import * as fn from './util2.js';
  5. console.log(str);
  6. fn1();
  7. fn2();
  8. fn.fn1();
  9. fn.fn2();

export default 默认输出这个,然后在import的时候就会拿到默认输出的内容。例子中默认输出的a=100
export多个内容,在import时需要使用{}进行引用你需要的内容。

exportexport defaultexportsmodule.exports的区别

require: node 和 es6 都支持的引入
export / import : 只有es6 支持的导出引入
module.exports / exports: 只有 node 支持的导出

  1. module.exports 初始值为一个空对象 {}
  2. exports 是指向的 module.exports 的引用
  3. require() 返回的是 module.exports 而不是 exports

Node里面的模块系统遵循的是CommonJS规范。

CommonJS定义的模块分为: 模块标识(module)、模块定义(exports) 、模块引用(require)

在nodejs,exportsmodule.exports的引用,初始化时,它们都指向同一个{}对象。

对象在JS中属于引用类型,意思就是exportsmodule.exports是指向同一个内存地址的。

看下面的例子:

  1. exports.fn = function(){
  2. console.log('fn');
  3. }
  4. // 这两种情况的效果是一样的,上面说了exports与`module.exports初始化同一个对象,所以两种定义方就是给这个同对象定义了一个fn的属性,该属性值为一个函数。
  5. module.exports.fn = function(){
  6. console.log('fn');
  7. }
  1. exports = function(){
  2. console.log('fn');
  3. }
  4. // 这两种情况就不一样了。上面的exports想当于指向了另一个内存地址。而下面这种情况是可以正常导出的。
  5. module.exports = function(){
  6. console.log('fn');
  7. }

exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。

  1. // sayHello.js
  2. function sayHello() {
  3. console.log('hello');
  4. }
  5. module.exports = sayHello;
  6. // app.js
  7. var sayHello = require('./sayHello');
  8. sayHello();

定义一个sayHello模块,模块里定义了一个sayHello方法,通过替换当前模块exports对象的方式将sayHello方法导出。
在app.js中加载这个模块,得到的是一个函数,调用该函数,控制台打印hello。

  1. // sayWorld.js
  2. module.exports = function(){
  3. console.log('world');
  4. }
  5. // app.js
  6. var sayWorld = require('./sayWorld'); // 匿名替换
  7. sayWorld();

当要导出多个变量怎么办呢?这个时候替换当前模块对象的方法就不实用了,我们需要用到exports对象。

  1. // userExports.js
  2. exports.a = function () {
  3. console.log('a exports');
  4. }
  5. exports.b = function () {
  6. console.log('b exports');
  7. }
  8. // app.js
  9. var useExports = require('./userExports');
  10. useExports.a();
  11. useExports.b();
  12. // a exports
  13. // b exports

当然,将useExports.js改成这样也是可以的:

  1. // userExports.js
  2. module.exports.a = function () {
  3. console.log('a exports');
  4. }
  5. module.exports.b = function () {
  6. console.log('b exports');
  7. }

在实际开发当中可以只使用module.exports避免造成不必要的问题。

开发环境配置

babel

Babel中文网

  1. // .babelrc
  2. {
  3. "presets": ["es2015","latest"],
  4. "plugins": []
  5. }

webpack

四大维度解锁webpack3笔记

rollup.js

Rollup.js官网

rollup 功能单一(打包js模块化), webpack功能强大
工具尽量功能单一,可继承,可扩展

  1. // .babelrc
  2. {
  3. "presets":[
  4. ["latest", {
  5. "es2015":{
  6. "modules": false
  7. }
  8. }]
  9. ],
  10. "plugins":["external-helpers"]
  11. }
  1. // rollup.config.js
  2. import babel from 'rollup-plugin-babel';
  3. import resolve from 'rollup-plugin-node-resolve';
  4. export default {
  5. entry: 'src/index.js',
  6. format: 'umd',
  7. plugins: [
  8. resolve(),
  9. babel({
  10. exclude: 'node_modules/**'
  11. })
  12. ],
  13. dest: 'build/bundle.js'
  14. }
  1. // package.json
  2. ...
  3. "scripts":{
  4. "start": "rollup -c rollup.config.js"
  5. }
  6. ...

npm run start

关于JS众多模块化标准

Class和普通构造函数有何区别

JS构造函数

  1. // 构造函数
  2. function MathHandle(x, y){
  3. this.x = x;
  4. this.y = y;
  5. }
  6. // 原型扩展
  7. MathHandle.prototype.add = function(){
  8. return this.x + this.y;
  9. }
  10. // 创建实例
  11. var m = new ManthHandle(1,2);
  12. console.log(m.add()); // 3

Class基本语法

  1. class MathHandle { // 直接跟大括号
  2. constructor(x, y) {
  3. this.x = x;
  4. this.y = y;
  5. }
  6. add() {
  7. return this.x + this.y;
  8. }
  9. }
  10. const m = new ManthHandle(1,2);
  11. console.log(m.add()); // 3

语法糖

typeof MathHandle = 'function'
MathHandle其实是个function,‘构造函数’
MathHandle === MathHandle.prototype.constructor

继承

原生js继承

  1. // 动物
  2. function Animal() {
  3. this.eat = function() {
  4. console.log('animal eat');
  5. }
  6. }
  7. // 狗
  8. function Dog() {
  9. this.bark = function(){
  10. console.log('dog bark');
  11. }
  12. }
  13. // 绑定原型,实现继承
  14. Dog.prototype = new Animal();
  15. // 实例化一只狗
  16. var hashiqi = new Dog();
  17. // hashiqi就有了eat方法
  18. hashiqi.eat(); // animal eat

廖雪峰老师的原型继承:点这里

ES6继承

  1. class Animal {
  2. constructor(name){
  3. this.name = name;
  4. }
  5. eat() {
  6. console.log(`${this.name} eat`);
  7. }
  8. }
  9. class Dog extends Animal { // extends 继承
  10. constructor(name){
  11. super(name); // 必须* 记得用super调用父类的构造方法!
  12. this.name = name;
  13. }
  14. say() {
  15. console.log(`${this.name} say`);
  16. }
  17. }
  18. const dog = new Dog('hashiqi');
  19. dog.eat(); // hashiqi eat

Promise 的基础使用

解决回调地狱(Callback Hell)用同步的方式来书写异步的代码
详细点的Promise:点这里

Promise 基础语法

  1. new Promise((resolve, reject) => {
  2. // 一段耗时很长的异步操作
  3. .....
  4. resolve(); // 数据处理完成
  5. reject(); // 数据处理出错
  6. }).then(function A() {
  7. // 成功,下一步
  8. }, function B(){
  9. // 失败,做相应处理
  10. })

我最开始接触到Promise的时候,一直傻了吧唧的在想resolve()reject()在什么时候调用。
resolve()reject()就是为后面then()中的两个函数服务的。

resolve和reject

  1. new Promise((resolve, reject) => {
  2. setTimeout(()=>{
  3. resolve('good,我要传给then里的一个函数');
  4. },2000);
  5. setTimeout(()=>{
  6. reject('错了,把我给我then里的第二个函数');
  7. },2000);
  8. }).then(value => {
  9. console.log(value); // good,我要传给then里的一个函数
  10. },value => {
  11. console.log(value); // 错了,把我给我then里的第二个函数
  12. });

来个实际的例子

  1. /**
  2. * 基于jquery封装一个promise ajax请求
  3. * @param {[type]} param [选项]
  4. * @return {[type]} [description]
  5. */
  6. request(param){
  7. return new Promise((resolve,reject) => {
  8. $.ajax({
  9. type : param.type || 'get',
  10. url : param.url || '',
  11. dataType : param.dataType || 'json',
  12. data : param.data || null,
  13. success:(res)=>{ // 用箭头函数避免this指向问题
  14. if (0 === res.status) {
  15. typeof resolve === 'function'&&resolve(res.data, res.msg); // 成功就把请求到的数据用resolve返回,这样就可以在then的第一个函数里拿到值了
  16. } else {
  17. typeof reject === 'function'&&reject(res.msg || res.data); // 失败就返回错误信息
  18. }
  19. },
  20. error:(err)=>{ // 这个失败是请求失败,上面那个失败是请求成功发送了,但是没有拿到数据失败了
  21. typeof reject === 'function'&&reject(err.statusText);
  22. }
  23. })
  24. })
  25. }

generator 生成器

Generator 函数是 ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。普通函数 -- 一路到底;generator函数 -- 中间能停

  1. // 加个*
  2. function *show() {
  3. console.log('a')
  4. yield;
  5. console.log('b')
  6. }
  7. let genObj = show()
  8. genObj.next()
  9. alert('中间暂停')
  10. genObj.next()

yield

传参

  1. function *show() {
  2. console.log('a')
  3. let num = yield;
  4. console.log(num) // 12? 5?
  5. }
  6. let genObj = show()
  7. genObj.next(12)
  8. genObj.next(5)


蓝色的是第一个next,红色是第二个next,所以num = 5

返回

  1. function *show() {
  2. console.log('a')
  3. yield;
  4. console.log('b')
  5. return 13
  6. }
  7. let genObj = show()
  8. let res1 = genObj.next()
  9. console.log(res1)
  10. // {value: 12, done: false}
  11. let res2 = genObj.next()
  12. console.log(res2)
  13. // {value: 13, done: true}

ES6常用其他功能

let/const

let constvar都是用来定义变量的,不同的是let自带作用域,const不能重复赋值。

  1. let name = 'FinGet'
  2. while (true) {
  3. let name = 'GetFin'
  4. console.log(name) //GetFin
  5. break
  6. }
  7. console.log(name) //FinGet

let定义的变量只在包含它的代码块内有用

  1. const PI = 3.1415926;
  2. PI = 3.14; // 错误

多行字符串/模板变量

  1. let name = 'FinGet';
  2. let age = 22;
  3. // js
  4. var str = '我是'+ name+',今年'+age+'岁'; // 很麻烦
  5. let str1 = `我是${name},今年${age}岁`; // 简单多了

模板字符串就是用`(Tab键上面那个)包含,变量就是用${}`表示

解构赋值

  1. let obj = {
  2. name: 'FinGet',
  3. age: 22,
  4. job: '前端',
  5. addr: '成都'
  6. }
  7. let {name,age} = obj;
  8. console.log(name); // FinGet
  9. console.log(age); // 22

还可以反过来:

  1. let name = 'FinGet';
  2. let age = 22;
  3. let job = '前端';
  4. let addr = '成都';
  5. let obj = {name,age,job,addr};
  6. //obj = {name: 'FinGet',age: 22,job: '前端',addr: '成都'}

块级作用域

另外一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:

  1. // js
  2. var a = [];
  3. for (var i = 0; i < 10; i++) {
  4. a[i] = function () {
  5. console.log(i);
  6. };
  7. }
  8. a[6](); // 10

let 自带块级作用域

  1. // ES6
  2. var a = [];
  3. for (let i = 0; i < 10; i++) {
  4. a[i] = function () {
  5. console.log(i);
  6. };
  7. }
  8. a[6](); // 6

原生js想实现这种效果,需要用到闭包:

  1. var a = [];
  2. for (var i = 0; i < 10; i++) {
  3. (function(j){ // 立即执行函数
  4. a[j] = function() {
  5. console.log(j);
  6. }
  7. }(i))
  8. }
  9. a[6](); // 6

立即执行函数形成了一个块级作用域,将参数j保存了下来,并不会被‘污染’,原生js没有块级作用域,varfor中定义的变量是个全局变量,可以在外部访问,也就可以被改变,所以每次for循环都是重置修改i的值,导致最后只能输出10。

函数默认参数与rest

default很简单,意思就是默认值。大家可以看下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'来指定默认值。

  1. function animal(type){
  2. type = type || 'cat'
  3. console.log(type)
  4. }
  5. animal()

如果用ES6我们而已直接这么写:

  1. function animal(type = 'cat'){
  2. console.log(type)
  3. }
  4. animal(); // cat

最后一个rest语法也很简单,直接看例子:

  1. function animals(...types){
  2. console.log(types)
  3. }
  4. animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]

而如果不用ES6的话,我们则得使用ES5的arguments。

箭头函数

  1. // js函数
  2. function (a,b){
  3. console.log(a+b);
  4. }
  1. // es6箭头函数
  2. (a,b) => {
  3. console.log(a+b);
  4. }

function去掉,在(){}之间加上=>


当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this

原型

下面内容为转载的,原地址,写的真的很好!

构造函数创建对象

我们先使用构造函数创建一个对象:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. person.name = 'Kevin';
  5. console.log(person.name) // Kevin

在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person。

很简单吧,接下来进入正题:

prototype

每个函数都有一个 prototype 属性,就是我们经常在各种例子中看到的那个 prototype ,比如:

  1. function Person() {
  2. }
  3. // 虽然写在注释里,但是你要注意:
  4. // prototype是函数才会有的属性
  5. Person.prototype.name = 'Kevin';
  6. var person1 = new Person();
  7. var person2 = new Person();
  8. console.log(person1.name) // Kevin
  9. console.log(person2.name) // Kevin

那这个函数的 prototype 属性到底指向的是什么呢?是这个函数的原型吗?

其实,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。

那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

让我们用一张图表示构造函数和实例原型之间的关系:

构造函数和实例原型的关系图
在这张图中我们用 Object.prototype 表示实例原型。

那么我们该怎么表示实例与实例原型,也就是 personPerson.prototype 之间的关系呢,这时候我们就要讲到第二个属性:

proto

这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

为了证明这一点,我们可以在火狐或者谷歌中输入:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ === Person.prototype); // true

于是我们更新下关系图:

实例与实例原型的关系图

既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?

constructor

指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。

为了验证这一点,我们可以尝试:

  1. function Person() {
  2. }
  3. console.log(Person === Person.prototype.constructor); // true

所以再更新下关系图:

实例原型与构造函数的关系图

综上我们已经得出:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ == Person.prototype) // true
  5. console.log(Person.prototype.constructor == Person) // true
  6. // 顺便学习一个ES5的方法,可以获得对象的原型
  7. console.log(Object.getPrototypeOf(person) === Person.prototype) // true

了解了构造函数、实例原型、和实例之间的关系,接下来我们讲讲实例和原型的关系:

实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

举个例子:

  1. function Person() {
  2. }
  3. Person.prototype.name = 'Kevin';
  4. var person = new Person();
  5. person.name = 'Daisy';
  6. console.log(person.name) // Daisy
  7. delete person.name;
  8. console.log(person.name) // Kevin

在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。

但是当我们删除了 personname 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。

但是万一还没有找到呢?原型的原型又是什么呢?

原型的原型

在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

  1. var obj = new Object();
  2. obj.name = 'Kevin'
  3. console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype ,所以我们再更新下关系图:

原型的原型关系图

原型链

Object.prototype 的原型呢?

null,我们可以打印:

  1. console.log(Object.prototype.__proto__ === null) // true

然而 null 究竟代表了什么呢?

引用阮一峰老师的 《undefined与null的区别》 就是:

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

最后一张关系图也可以更新为:

原型链示意图

顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

补充

最后,补充三点大家可能不会注意的地方:

constructor

首先是 constructor 属性,我们看个例子:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

  1. person.constructor === Person.prototype.constructor

proto

其次是 __proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

真的是继承吗?

最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

推荐阅读:阮一峰Javascript继承机制的设计思想

异步

什么是单线程,和异步有什么关系

单线程-只有一个线程,只做一件事。JS之所以是单线程,取决于它的实际使用,例如JS不可能同添加一个DOM和删除这个DOM,所以它只能是单线程的。

  1. console.log(1);
  2. alert(1);
  3. console.log(2);

上面这个例子中,当执行了alert(1),如果用户不点击确定按钮,console.log(2)是不会执行的。

为了利用多核CPU的计算能力,HTML5提出WebWorker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

js异步

  1. console.log(100);
  2. setTimeout(function(){
  3. console.log(200);
  4. },1000)
  5. console.log(300);
  6. console.log(400);
  7. console.log(400);
  8. .... // 这里来很多很多个console.log(400); 结果就是打印完所有的400,等一秒再打印200

event-loop

event-loop

文字解释

上面那个例子的执行效果就是这样的:

实例分析:

这个例子中有两种情况,取决于ajax的返回时间,如果ajax时间小于100ms它就先放进异步队列

Jquery Deferred

Jquery1.5前后的变化

  1. var ajax = $.ajax({
  2. url: 'data.json',
  3. success: function(){
  4. console.log('success1');
  5. console.log('success2');
  6. console.log('success3');
  7. },
  8. error: function(){
  9. console.log('error');
  10. }
  11. })
  12. console.log(ajax); // 返回一个xhr对象
  1. // 链式操作
  2. var ajax = $.ajax('data.json');
  3. ajax.done(function(){
  4. console.log('success1');
  5. }).fail(function(){
  6. console.log('error');
  7. }).done(function(){
  8. console.log()
  9. })
  10. console.log(ajax); // 返回一个deferred对象

使用Jquery Deferred

  1. // 给出一段非常简单的异步操作代码,使用setTimeout函数
  2. var wait = function(){
  3. var task = function(){
  4. console.log('执行完成)
  5. }
  6. setTimeout(task, 2000);
  7. }
  8. wait();

新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤

  1. function waitHandle(){
  2. var dtd = $.Deferred(); // 创建一个deferred对象
  3. var wait = function(dtd){ // 要求传入一个deferred对象
  4. var task = function(){
  5. console.log('执行完成');
  6. dtd.resolve(); // 表示异步任务已经完成
  7. // dtd.reject(); // 表示异步任务失败或出错
  8. }
  9. setTimeout(task, 2000);
  10. return dtd; // 要求返回deferred对象
  11. }
  12. // 注意,这里一定要有返回值
  13. return wait(dtd);
  14. }
  15. var w = waitHandle();
  16. w.then(function(){
  17. console.log('ok 1');
  18. }, function(){
  19. console.log('err 1');
  20. }).then(function(){
  21. console.log('ok 2');
  22. }, function(){
  23. console.log('err 2');
  24. })

当执行dtd.reject()时:

  1. var w = waitHandle();
  2. w.then(function(){
  3. console.log('ok 1');
  4. }, function(){
  5. console.log('err 1');
  6. })
  7. // 不能链式
  8. w.then(function(){
  9. console.log('ok 2');
  10. }, function(){
  11. console.log('err 2');
  12. })

上面封装的waitHandle方法,由于直接返回了dtd(deferred对象),所以用户可以直接调用w.reject()方法,导致无论是成功还是失败,最后都走失败。

  1. // 修改
  2. function waitHandle(){
  3. var dtd = $.Deferred();
  4. var wait = function(dtd){
  5. var task = function(){
  6. console.log('执行完成');
  7. dtd.resolve();
  8. }
  9. setTimeout(task, 2000);
  10. return dtd.promise(); // 注意这里返回的是promise,而不是直接返回deferred对象
  11. }
  12. return wait(dtd);
  13. }

ES6的Promise:点这里

  1. // promise封装一个异步加载图片的方法
  2. function loadImg(src) {
  3. var promise = new Promise(function(resolve,reject){
  4. var img = document.createElement('img');
  5. img.onload = function(){
  6. resolve(img)
  7. }
  8. img.onerror = function(){
  9. reject('图片加载失败')
  10. }
  11. img.src = src;
  12. })
  13. return promise;
  14. }

async/await

这是ES7提案中的,现在babel已经开始支持了,koa也是用async/await实现的。

  1. // 伪代码
  2. const load = async function(){
  3. const result1 = await loadImg(src1);
  4. console.log(result1);
  5. const result2 = await loadImg(src2);
  6. console.log(result2);
  7. }
  8. load();

虚拟DOM--Virtual Dom

什么是虚拟DOM

重绘和回流

页面渲染过程:
重绘和回流

模拟虚拟DOM

  1. <ul id="list">
  2. <li class="item">Item 1</li>
  3. <li class="item">Item 2</li>
  4. </ul>
  1. // js模拟虚拟DOM
  2. {
  3. tag: 'ul',
  4. attrs:{
  5. id: 'list'
  6. },
  7. children:[
  8. {
  9. tag: 'li',
  10. attrs: {className: 'item'},
  11. children: ['Item 1']
  12. },
  13. {
  14. tag: 'li',
  15. attrs: {className: 'item'},
  16. children: ['Item 2']
  17. }
  18. ]
  19. }
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script>
  7. </head>
  8. <body>
  9. <div id="container"></div>
  10. <button id="btn-change">change</button>
  11. <script>
  12. var data = [
  13. {name: '张三',age: '20',address: '北京'},
  14. {name: '王五',age: '22',address: '成都'},
  15. {name: '李四',age: '21',address: '上海'}
  16. ]
  17. // 渲染函数
  18. function render(data) {
  19. var $container = $('#container');
  20. // 清空容器,重要!!!
  21. $container.html('');
  22. // 拼接 table
  23. var $table = $('<table>');
  24. $table.append($('<tr><td>name</td><td>age</td><td>address</td>/tr>'));
  25. data.forEach(function (item) {
  26. $table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td>/tr>'))
  27. });
  28. // 渲染到页面
  29. $container.append($table);
  30. }
  31. $('#btn-change').click(function () {
  32. data[1].age = 30;
  33. data[2].address = '深圳';
  34. // re-render 再次渲染
  35. render(data);
  36. })
  37. // 页面加载完立刻执行(初次渲染)
  38. render(data);
  39. </script>
  40. </body>
  41. </html>

虽然只改变了两个数据,但是整个table都闪烁了(回流&重绘)

虚拟DOM如何应用,核心API是什么

介绍 snabbdom

snabbdom GitHub地址

官网例子:

  1. var snabbdom = require('snabbdom');
  2. var patch = snabbdom.init([ // Init patch function with chosen modules
  3. require('snabbdom/modules/class').default, // makes it easy to toggle classes
  4. require('snabbdom/modules/props').default, // for setting properties on DOM elements
  5. require('snabbdom/modules/style').default, // handles styling on elements with support for animations
  6. require('snabbdom/modules/eventlisteners').default, // attaches event listeners
  7. ]);
  8. var h = require('snabbdom/h').default; // helper function for creating vnodes
  9. var container = document.getElementById('container');
  10. // h函数生成一个虚拟节点
  11. var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
  12. h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
  13. ' and this is just normal text',
  14. h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
  15. ]);
  16. // Patch into empty DOM element – this modifies the DOM as a side effect
  17. patch(container, vnode); // 把vnode加入到container中
  18. // 数据改变,重新生成一个newVnode
  19. var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
  20. h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
  21. ' and this is still just normal text',
  22. h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
  23. ]);
  24. // Second `patch` invocation
  25. // 将newVnode更新到之前的vnode中,从而更新视图
  26. patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

snabbdom h 函数

  1. var vnode = h('ul#list',{},[
  2. h('li.item',{},'Item 1'),
  3. h('li.item',{},'Item 2')
  4. ])
  5. {
  6. tag: 'ul',
  7. attrs:{
  8. id: 'list'
  9. },
  10. children:[
  11. {
  12. tag: 'li',
  13. attrs: {className: 'item'},
  14. children: ['Item 1']
  15. },
  16. {
  17. tag: 'li',
  18. attrs: {className: 'item'},
  19. children: ['Item 2']
  20. }
  21. ]
  22. }

snabbdom patch 函数

  1. var vnode = h('ul#list',{},[
  2. h('li.item',{},'Item 1'),
  3. h('li.item',{},'Item 2')
  4. ])
  5. var container = document.getElementById('container');
  6. patch(container, vnode);
  7. // 模拟改变
  8. var btnChange = document.getElementById('btn-change');
  9. btnChange.addEventListener('click',function(){
  10. var newVnode = h('ul#list',{},[
  11. h('li.item',{},'Item 111'),
  12. h('li.item',{},'Item 222'),
  13. h('li.item',{},'Item 333')
  14. ])
  15. patch(vnode, newVnode);
  16. })

snabbdom例子

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
  7. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
  8. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
  9. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
  10. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
  11. <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
  12. </head>
  13. <body>
  14. <div id="container"></div>
  15. <button id="btn-change">change</button>
  16. <script>
  17. var snabbdom = window.snabbdom;
  18. // 定义 patch
  19. var patch = snabbdom.init([
  20. snabbdom_class,
  21. snabbdom_props,
  22. snabbdom_style,
  23. snabbdom_eventlisteners
  24. ])
  25. // 定义 h
  26. var h = snabbdom.h;
  27. var container = document.getElementById('container');
  28. // 生成 vnode
  29. var vnode = h('ul#list',{},[
  30. h('li.item',{},'Item 1'),
  31. h('li.item',{},'Item 2')
  32. ])
  33. patch(container, vnode);
  34. // 模拟数据改变
  35. var btnChange = document.getElementById('btn-change');
  36. btnChange.addEventListener('click',function(){
  37. var newVnode = h('ul#list',{},[
  38. h('li.item',{},'Item 1'),
  39. h('li.item',{},'Item 222'),
  40. h('li.item',{},'Item 333')
  41. ])
  42. patch(vnode, newVnode);
  43. })
  44. </script>
  45. </body>
  46. </html>

看图,只有修改了的数据才进行了刷新,减少了DOM操作,这其实就是vnode与newVnode对比,找出改变了的地方,然后只重新渲染改变的

重做之前的demo

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <body>
  8. <div id="container"></div>
  9. <button id="btn-change">change</button>
  10. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom.js"></script>
  11. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script>
  12. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script>
  13. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script>
  14. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script>
  15. <script src="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script>
  16. <script type="text/javascript">
  17. var snabbdom = window.snabbdom;
  18. // 定义关键函数 patch
  19. var patch = snabbdom.init([
  20. snabbdom_class,
  21. snabbdom_props,
  22. snabbdom_style,
  23. snabbdom_eventlisteners
  24. ]);
  25. // 定义关键函数 h
  26. var h = snabbdom.h;
  27. // 原始数据
  28. var data = [
  29. {name: '张三',age: '20',address: '北京'},
  30. {name: '王五',age: '22',address: '成都'},
  31. {name: '李四',age: '21',address: '上海'}
  32. ]
  33. // 把表头也放在 data 中
  34. data.unshift({
  35. name: '姓名',
  36. age: '年龄',
  37. address: '地址'
  38. });
  39. var container = document.getElementById('container')
  40. // 渲染函数
  41. var vnode;
  42. function render(data) {
  43. var newVnode = h('table', {}, data.map(function (item) {
  44. var tds = [];
  45. var i;
  46. for (i in item) {
  47. if (item.hasOwnProperty(i)) {
  48. tds.push(h('td', {}, item[i] + ''));
  49. }
  50. }
  51. return h('tr', {}, tds)
  52. }));
  53. if (vnode) {
  54. // re-render
  55. patch(vnode, newVnode);
  56. } else {
  57. // 初次渲染
  58. patch(container, newVnode);
  59. }
  60. // 存储当前的 vnode 结果
  61. vnode = newVnode;
  62. }
  63. // 初次渲染
  64. render(data)
  65. var btnChange = document.getElementById('btn-change')
  66. btnChange.addEventListener('click', function () {
  67. data[1].age = 30
  68. data[2].address = '深圳'
  69. // re-render
  70. render(data)
  71. })
  72. </script>
  73. </body>
  74. </html>

核心API

简单介绍 diff 算法

什么是 diff 算法

这里有两个文本文件:

借用git bashdiff 命令可以比较两个文件的区别:

在线diff工具

虚拟DOM ---> DOM

  1. // 一个实现流程,实际情况还很复杂
  2. function createElement(vnode) {
  3. var tag = vnode.tag // 'ul'
  4. var attrs = vnode.attrs || {}
  5. var children = vnode.children || []
  6. if (!tag) {
  7. return null
  8. }
  9. // 创建真实的 DOM 元素
  10. var elem = document.createElement(tag)
  11. // 属性
  12. var attrName
  13. for (attrName in attrs) {
  14. if (attrs.hasOwnProperty(attrName)) {
  15. // 给 elem 添加属性
  16. elem.setAttribute(attrName, attrs[attrName])
  17. }
  18. }
  19. // 子元素
  20. children.forEach(function (childVnode) {
  21. // 给 elem 添加子元素
  22. elem.appendChild(createElement(childVnode)) // 递归
  23. })
  24. // 返回真实的 DOM 元素
  25. return elem
  26. }

vnode ---> newVnode

  1. function updateChildren(vnode, newVnode) {
  2. var children = vnode.children || [];
  3. var newChildren = newVnode.children || [];
  4. children.forEach(function (childVnode, index) {
  5. var newChildVnode = newChildren[index];
  6. if (childVnode.tag === newChildVnode.tag) {
  7. // 深层次对比,递归
  8. updateChildren(childVnode, newChildVnode);
  9. } else {
  10. // 替换
  11. replaceNode(childVnode, newChildVnode);
  12. }
  13. })
  14. }
  15. function replaceNode(vnode, newVnode) {
  16. var elem = vnode.elem; // 真实的 DOM 节点
  17. var newElem = createElement(newVnode);
  18. // 替换
  19. }

MVVM 和 Vue

MVVM

Jquery 与 框架的区别

jquery 实现 todo-list

  1. <div>
  2. <input type="text" name="" id="txt-title">
  3. <button id="btn-submit">submit</button>
  4. </div>
  5. <div>
  6. <ul id="ul-list"></ul>
  7. </div>
  8. <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
  9. <script type="text/javascript">
  10. var $txtTitle = $('#txt-title');
  11. var $btnSubmit = $('#btn-submit');
  12. var $ulList = $('#ul-list');
  13. $btnSubmit.click(function () {
  14. var title = $txtTitle.val();
  15. if (!title) {
  16. return
  17. }
  18. var $li = $('<li>' + title + '</li>');
  19. $ulList.append($li);
  20. $txtTitle.val('');
  21. })
  22. </script>

vue 实现 todo-list

  1. <div id="app">
  2. <div>
  3. <input v-model="title">
  4. <button v-on:click="add">submit</button>
  5. </div>
  6. <div>
  7. <ul>
  8. <li v-for="item in list">{{item}}</li>
  9. </ul>
  10. </div>
  11. </div>
  12. <script type="text/javascript">
  13. // data 独立
  14. var data = {
  15. title: '',
  16. list: []
  17. }
  18. // 初始化 Vue 实例
  19. var vm = new Vue({
  20. el: '#app',
  21. data: data,
  22. methods: {
  23. add: function () {
  24. this.list.push(this.title);
  25. this.title = '';
  26. }
  27. }
  28. })
  29. </script>

两者的区别

MVVM的理解

View 通过 事件绑定 (DOM Listeners) 操作Model; Model通过 数据绑定 (Data Bindings)操作View。

Vue 三要素

Vue中如何实现响应式

什么是响应式

  1. var vm = new Vue({
  2. el: '#app',
  3. data: {
  4. name: 'zhangsan',
  5. age: 20
  6. }
  7. })
  8. // vm.name = 'zhangsan'
  9. // vm.age = '20'

Object.defineProperty,Vue核心函数

  1. var obj = {
  2. name: 'zhangsan',
  3. age: 25
  4. }
  5. console.log(obj.name); // 获取属性的时候,如何监听
  6. obj.age = 26; // 赋值属性的时候,如何监听

上面是无法监听对象的属性的访问以及赋值操作的,直接就产生了操作的结果。

  1. var obj = {}
  2. var _name = 'shangsan'
  3. Object.defineProperty(obj, 'name', {
  4. get: function () {
  5. console.log('get', _name) // 监听
  6. return _name
  7. },
  8. set: function (newVal) {
  9. console.log('set', newVal) // 监听
  10. _name = newVal
  11. }
  12. })
  13. console.log(obj.name); // 可以监听到
  14. obj.name = 'lisi'; // 可以监听到

Vue 中何如解析模板

模板是什么

  1. <div id="app">
  2. <div>
  3. <input v-model="title">
  4. <button v-on:click="add">submit</button>
  5. </div>
  6. <div>
  7. <ul>
  8. <li v-for="item in list">{{item}}</li>
  9. </ul>
  10. </div>
  11. </div>

render函数

with -- 实际开发不推荐用
  1. var obj = {
  2. name: 'zhangsan',
  3. age: 20,
  4. getAddress: function () {
  5. alert('beijing')
  6. }
  7. }
  8. // 不使用with
  9. function fn() {
  10. alert(obj.name)
  11. alert(obj.age)
  12. obj.getAddress()
  13. }
  14. fn()
  15. // 使用with
  16. function fn1() {
  17. with(obj) {
  18. alert(age)
  19. alert(name)
  20. getAddress()
  21. }
  22. }
  23. fn1()
render
  1. <div id="app">
  2. <p>{{price}}</p>
  3. </div>
  4. <script>
  5. var vm = new Vue({
  6. el: '#app',
  7. data: {
  8. price: 100
  9. }
  10. })
  11. </script>

模板将变成下面这个样子:

  1. function render() {
  2. with(this) { // this 就是 vm
  3. return _c(
  4. 'div',
  5. {
  6. attrs: {'id': 'app'}
  7. },
  8. [
  9. _c('p', [_v(_s(price))])
  10. ]
  11. )
  12. }
  13. }

看todo-list的render

在vue源码里alert render 函数

以上面vue实现的todolist为例:

  1. with(this){ // this 就是 vm
  2. return _c( // _c创建一个标签
  3. 'div',
  4. {
  5. attrs:{"id":"app"}
  6. },
  7. [
  8. _c(
  9. 'div',
  10. [
  11. _c(
  12. 'input',
  13. {
  14. directives:[
  15. {
  16. name:"model",
  17. rawName:"v-model",
  18. value:(title),
  19. expression:"title"
  20. }
  21. ],
  22. domProps:{
  23. "value":(title)
  24. },
  25. on:{
  26. "input":function($event){
  27. if($event.target.composing)return;
  28. title=$event.target.value
  29. }
  30. }
  31. }
  32. ),
  33. _v(" "),
  34. _c(
  35. 'button',
  36. {
  37. on:{
  38. "click":add
  39. }
  40. },
  41. [_v("submit")]
  42. )
  43. ]
  44. ),
  45. _v(" "),
  46. _c('div',
  47. [
  48. _c(
  49. 'ul',
  50. _l((list),function(item){return _c('li',[_v(_s(item))])}) // _l 解析 v-for 循环
  51. )
  52. ]
  53. )
  54. ]
  55. )
  56. }
render 与 Vdom

可以先看一下virtualDom

vue 的整个实现流程


react 与 组件化

react 实现一个todolist

  1. // Input 组件
  2. import React, { Component } from 'react'
  3. class Input extends Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = {
  7. title: ''
  8. }
  9. }
  10. render() {
  11. return (
  12. <div>
  13. <input value={this.state.title} onChange={this.changeHandle.bind(this)}/>
  14. <button onClick={this.clickHandle.bind(this)}>submit</button>
  15. </div>
  16. )
  17. }
  18. changeHandle(event) {
  19. this.setState({
  20. title: event.target.value
  21. })
  22. }
  23. clickHandle() {
  24. const title = this.state.title;
  25. const addTitle = this.props.addTitle;
  26. addTitle(title); // 重点!!!
  27. this.setState({
  28. title: ''
  29. })
  30. }
  31. }
  32. export default Input;
  1. // list 组件
  2. import React, { Component } from 'react'
  3. class List extends Component {
  4. constructor(props) {
  5. super(props);
  6. }
  7. render() {
  8. const list = this.props.data;
  9. return (
  10. <ul>
  11. {
  12. list.map((item, index) => {
  13. return <li key={index}>{item}</li>
  14. })
  15. }
  16. </ul>
  17. )
  18. }
  19. }
  20. export default List;
  1. // todo 页面
  2. import React, { Component } from 'react'
  3. import Input from './input/index.js'
  4. import List from './list/index.js'
  5. class Todo extends Component {
  6. constructor(props) {
  7. super(props);
  8. this.state = {
  9. list: ['a', 'b']
  10. }
  11. }
  12. render() {
  13. return (
  14. <div>
  15. <Input addTitle={this.addTitle.bind(this)}/>
  16. <List data={this.state.list}/>
  17. </div>
  18. )
  19. }
  20. addTitle(title) {
  21. const currentList = this.state.list
  22. this.setState({
  23. list: currentList.concat(title)
  24. })
  25. }
  26. }
  27. export default Todo;

组件化的理解

组件的封装

组件的复用

JSX 本质是什么

JSX 语法

JS表达式

  1. ReactDOM.render(
  2. <div>
  3. <h1>{1+1}</h1>
  4. </div>,
  5. document.getElementById('example')
  6. );
  7. ReactDOM.render(
  8. <div>
  9. <h1>{i == 1 ? 'True!' : 'False'}</h1>
  10. </div>,
  11. document.getElementById('example')
  12. );

样式

  1. var myStyle = {
  2. fontSize: 100,
  3. color: '#FF0000'
  4. };
  5. ReactDOM.render(
  6. <div>
  7. <h1 style = {myStyle}>JSX样式</h1>
  8. <p style={{color:'red',fontSize:'20px'}}>内联样式</p>
  9. <div className="class">类名</div>
  10. </div>,
  11. document.getElementById('example')
  12. );

注释

  1. ReactDOM.render(
  2. <div>
  3. <h1>JSX语法</h1>
  4. {/*注释...*/}
  5. </div>,
  6. document.getElementById('example')
  7. );

循环

  1. ReactDOM.render(
  2. const list = [1,2,3,4,5]
  3. <div>
  4. <ul>
  5. list.map((item, index) => {
  6. return <li key={index}>{item}</li>
  7. })
  8. </ul>
  9. </div>,
  10. document.getElementById('example')
  11. );

JSX 解析成JS

JSX到原生DOM是怎么炼成的

JSX 独立的标准

JSX 与 虚拟DOM

自定义组件的解析

setState 的过程

setState 的异步

setState 为何需要异步

setState 的过程

vue 与 react

模板的区别

hybrid

hybrid是什么

hybrid 即前端和客服端的混合开发

webview

WebView是一个专门用来显示网页的View子类。它使用WebKit渲染引擎来显示网页,并且支持包括前进,后退,放大,缩小,文本搜索等多种功能。

file 协议

File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样。

具体实现

hybrid 更新上线流程

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注