@Bios
2019-06-28T06:51:02.000000Z
字数 27067
阅读 1135
js
这是一个系列记录,也有自身的理解。只为了提高自己,坚持写完
分为8个大的部分:
现在基本上开发中都在使用ES6,浏览器环境支持不好,可以用babel插件来解决。
采用‘二八定律’,主要涉及ES6常用且重要的部分。
// util1.jsexport default {a : 100}const str = 'hello';export default str;// export default const str = 'hello'; X
// util2.jsexport function fn1() {console.log('fn1');}export function fn2() {console.log('fn2');}export const fn3 = ()=> {console.log('fn3');}
// index.jsimport str from './util1.js';import {fn1 , fn2} from './util2.js';import * as fn from './util2.js';console.log(str);fn1();fn2();fn.fn1();fn.fn2();
export default默认输出这个,然后在import的时候就会拿到默认输出的内容。例子中默认输出的a=100。
export多个内容,在import时需要使用{}进行引用你需要的内容。
export和export default与exports和module.exports的区别
require: node 和 es6 都支持的引入
export/import: 只有es6 支持的导出引入
module.exports/exports: 只有 node 支持的导出
module.exports 初始值为一个空对象 {}exports 是指向的 module.exports 的引用require() 返回的是 module.exports 而不是 exportsNode里面的模块系统遵循的是CommonJS规范。
CommonJS定义的模块分为: 模块标识(
module)、模块定义(exports) 、模块引用(require)
在nodejs,exports 是 module.exports的引用,初始化时,它们都指向同一个{}对象。
对象在JS中属于引用类型,意思就是exports和module.exports是指向同一个内存地址的。

看下面的例子:
exports.fn = function(){console.log('fn');}// 这两种情况的效果是一样的,上面说了exports与`module.exports初始化同一个对象,所以两种定义方就是给这个同对象定义了一个fn的属性,该属性值为一个函数。module.exports.fn = function(){console.log('fn');}
exports = function(){console.log('fn');}// 这两种情况就不一样了。上面的exports想当于指向了另一个内存地址。而下面这种情况是可以正常导出的。module.exports = function(){console.log('fn');}
exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。
// sayHello.jsfunction sayHello() {console.log('hello');}module.exports = sayHello;// app.jsvar sayHello = require('./sayHello');sayHello();
定义一个sayHello模块,模块里定义了一个sayHello方法,通过替换当前模块exports对象的方式将sayHello方法导出。
在app.js中加载这个模块,得到的是一个函数,调用该函数,控制台打印hello。
// sayWorld.jsmodule.exports = function(){console.log('world');}// app.jsvar sayWorld = require('./sayWorld'); // 匿名替换sayWorld();
当要导出多个变量怎么办呢?这个时候替换当前模块对象的方法就不实用了,我们需要用到exports对象。
// userExports.jsexports.a = function () {console.log('a exports');}exports.b = function () {console.log('b exports');}// app.jsvar useExports = require('./userExports');useExports.a();useExports.b();// a exports// b exports
当然,将useExports.js改成这样也是可以的:
// userExports.jsmodule.exports.a = function () {console.log('a exports');}module.exports.b = function () {console.log('b exports');}
在实际开发当中可以只使用
module.exports避免造成不必要的问题。
// .babelrc{"presets": ["es2015","latest"],"plugins": []}
npm initrollup.js需要的一些插件 npm i rollup rollup-plugin-node-resolve rollup-plugin-babel babel-core babel-plugin-external-helpers babel-preset-latest --save-devrollup 功能单一(打包js模块化), webpack功能强大
工具尽量功能单一,可继承,可扩展
// .babelrc{"presets":[["latest", {"es2015":{"modules": false}}]],"plugins":["external-helpers"]}
// rollup.config.jsimport 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'}
// package.json..."scripts":{"start": "rollup -c rollup.config.js"}...
npm run start
// 构造函数function MathHandle(x, y){this.x = x;this.y = y;}// 原型扩展MathHandle.prototype.add = function(){return this.x + this.y;}// 创建实例var m = new ManthHandle(1,2);console.log(m.add()); // 3
class MathHandle { // 直接跟大括号constructor(x, y) {this.x = x;this.y = y;}add() {return this.x + this.y;}}const m = new ManthHandle(1,2);console.log(m.add()); // 3
typeof MathHandle='function'
MathHandle其实是个function,‘构造函数’
MathHandle===MathHandle.prototype.constructor
// 动物function Animal() {this.eat = function() {console.log('animal eat');}}// 狗function Dog() {this.bark = function(){console.log('dog bark');}}// 绑定原型,实现继承Dog.prototype = new Animal();// 实例化一只狗var hashiqi = new Dog();// hashiqi就有了eat方法hashiqi.eat(); // animal eat
廖雪峰老师的原型继承:点这里
class Animal {constructor(name){this.name = name;}eat() {console.log(`${this.name} eat`);}}class Dog extends Animal { // extends 继承constructor(name){super(name); // 必须* 记得用super调用父类的构造方法!this.name = name;}say() {console.log(`${this.name} say`);}}const dog = new Dog('hashiqi');dog.eat(); // hashiqi eat
解决回调地狱(Callback Hell)用同步的方式来书写异步的代码
详细点的Promise:点这里
new Promise((resolve, reject) => {// 一段耗时很长的异步操作.....resolve(); // 数据处理完成reject(); // 数据处理出错}).then(function A() {// 成功,下一步}, function B(){// 失败,做相应处理})
我最开始接触到
Promise的时候,一直傻了吧唧的在想resolve()和reject()在什么时候调用。
resolve()和reject()就是为后面then()中的两个函数服务的。
new Promise((resolve, reject) => {setTimeout(()=>{resolve('good,我要传给then里的一个函数');},2000);setTimeout(()=>{reject('错了,把我给我then里的第二个函数');},2000);}).then(value => {console.log(value); // good,我要传给then里的一个函数},value => {console.log(value); // 错了,把我给我then里的第二个函数});
/*** 基于jquery封装一个promise ajax请求* @param {[type]} param [选项]* @return {[type]} [description]*/request(param){return new Promise((resolve,reject) => {$.ajax({type : param.type || 'get',url : param.url || '',dataType : param.dataType || 'json',data : param.data || null,success:(res)=>{ // 用箭头函数避免this指向问题if (0 === res.status) {typeof resolve === 'function'&&resolve(res.data, res.msg); // 成功就把请求到的数据用resolve返回,这样就可以在then的第一个函数里拿到值了} else {typeof reject === 'function'&&reject(res.msg || res.data); // 失败就返回错误信息}},error:(err)=>{ // 这个失败是请求失败,上面那个失败是请求成功发送了,但是没有拿到数据失败了typeof reject === 'function'&&reject(err.statusText);}})})}
Generator 函数是 ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。普通函数 -- 一路到底;generator函数 -- 中间能停
// 加个*function *show() {console.log('a')yield;console.log('b')}let genObj = show()genObj.next()alert('中间暂停')genObj.next()
function *show() {console.log('a')let num = yield;console.log(num) // 12? 5?}let genObj = show()genObj.next(12)genObj.next(5)
蓝色的是第一个next,红色是第二个next,所以num = 5
function *show() {console.log('a')yield;console.log('b')return 13}let genObj = show()let res1 = genObj.next()console.log(res1)// {value: 12, done: false}let res2 = genObj.next()console.log(res2)// {value: 13, done: true}
letconst与var都是用来定义变量的,不同的是let自带作用域,const不能重复赋值。
let name = 'FinGet'while (true) {let name = 'GetFin'console.log(name) //GetFinbreak}console.log(name) //FinGet
let定义的变量只在包含它的代码块内有用
const PI = 3.1415926;PI = 3.14; // 错误
let name = 'FinGet';let age = 22;// jsvar str = '我是'+ name+',今年'+age+'岁'; // 很麻烦let str1 = `我是${name},今年${age}岁`; // 简单多了
模板字符串就是用
`(Tab键上面那个)包含,变量就是用${}`表示
let obj = {name: 'FinGet',age: 22,job: '前端',addr: '成都'}let {name,age} = obj;console.log(name); // FinGetconsole.log(age); // 22
还可以反过来:
let name = 'FinGet';let age = 22;let job = '前端';let addr = '成都';let obj = {name,age,job,addr};//obj = {name: 'FinGet',age: 22,job: '前端',addr: '成都'}
另外一个var带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:
// jsvar a = [];for (var i = 0; i < 10; i++) {a[i] = function () {console.log(i);};}a[6](); // 10
let 自带块级作用域
// ES6var a = [];for (let i = 0; i < 10; i++) {a[i] = function () {console.log(i);};}a[6](); // 6
原生js想实现这种效果,需要用到闭包:
var a = [];for (var i = 0; i < 10; i++) {(function(j){ // 立即执行函数a[j] = function() {console.log(j);}}(i))}a[6](); // 6
立即执行函数形成了一个块级作用域,将参数j保存了下来,并不会被‘污染’,原生js没有块级作用域,
var在for中定义的变量是个全局变量,可以在外部访问,也就可以被改变,所以每次for循环都是重置修改i的值,导致最后只能输出10。
default很简单,意思就是默认值。大家可以看下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || 'cat'来指定默认值。
function animal(type){type = type || 'cat'console.log(type)}animal()
如果用ES6我们而已直接这么写:
function animal(type = 'cat'){console.log(type)}animal(); // cat
最后一个rest语法也很简单,直接看例子:
function animals(...types){console.log(types)}animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]
而如果不用ES6的话,我们则得使用ES5的arguments。
// js函数function (a,b){console.log(a+b);}
// es6箭头函数(a,b) => {console.log(a+b);}
把
function去掉,在()与{}之间加上=>
当我们使用箭头函数时,函数体内的
this对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this。
下面内容为转载的,原地址,写的真的很好!
我们先使用构造函数创建一个对象:
function Person() {}var person = new Person();person.name = 'Kevin';console.log(person.name) // Kevin
在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person。
很简单吧,接下来进入正题:
每个函数都有一个 prototype 属性,就是我们经常在各种例子中看到的那个 prototype ,比如:
function Person() {}// 虽然写在注释里,但是你要注意:// prototype是函数才会有的属性Person.prototype.name = 'Kevin';var person1 = new Person();var person2 = new Person();console.log(person1.name) // Kevinconsole.log(person2.name) // Kevin
那这个函数的 prototype 属性到底指向的是什么呢?是这个函数的原型吗?
其实,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。
那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
让我们用一张图表示构造函数和实例原型之间的关系:
在这张图中我们用 Object.prototype 表示实例原型。
那么我们该怎么表示实例与实例原型,也就是 person 和 Person.prototype 之间的关系呢,这时候我们就要讲到第二个属性:
这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
为了证明这一点,我们可以在火狐或者谷歌中输入:
function Person() {}var person = new Person();console.log(person.__proto__ === Person.prototype); // true
于是我们更新下关系图:

既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:
function Person() {}console.log(Person === Person.prototype.constructor); // true
所以再更新下关系图:

综上我们已经得出:
function Person() {}var person = new Person();console.log(person.__proto__ == Person.prototype) // trueconsole.log(Person.prototype.constructor == Person) // true// 顺便学习一个ES5的方法,可以获得对象的原型console.log(Object.getPrototypeOf(person) === Person.prototype) // true
了解了构造函数、实例原型、和实例之间的关系,接下来我们讲讲实例和原型的关系:
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
举个例子:
function Person() {}Person.prototype.name = 'Kevin';var person = new Person();person.name = 'Daisy';console.log(person.name) // Daisydelete person.name;console.log(person.name) // Kevin
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
但是万一还没有找到呢?原型的原型又是什么呢?
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
var obj = new Object();obj.name = 'Kevin'console.log(obj.name) // Kevin
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype ,所以我们再更新下关系图:

那 Object.prototype 的原型呢?
null,我们可以打印:
console.log(Object.prototype.__proto__ === null) // true
然而 null 究竟代表了什么呢?
引用阮一峰老师的 《undefined与null的区别》 就是:
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:

顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
最后,补充三点大家可能不会注意的地方:
首先是 constructor 属性,我们看个例子:
function Person() {}var person = new Person();console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
person.constructor === Person.prototype.constructor
其次是 __proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。
最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
单线程-只有一个线程,只做一件事。JS之所以是单线程,取决于它的实际使用,例如JS不可能同添加一个DOM和删除这个DOM,所以它只能是单线程的。
console.log(1);alert(1);console.log(2);
上面这个例子中,当执行了alert(1),如果用户不点击确定按钮,console.log(2)是不会执行的。
为了利用多核CPU的计算能力,HTML5提出
WebWorker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
console.log(100);setTimeout(function(){console.log(200);},1000)console.log(300);console.log(400);console.log(400);.... // 这里来很多很多个console.log(400); 结果就是打印完所有的400,等一秒再打印200

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

这个例子中有两种情况,取决于ajax的返回时间,如果ajax时间小于100ms它就先放进异步队列
var ajax = $.ajax({url: 'data.json',success: function(){console.log('success1');console.log('success2');console.log('success3');},error: function(){console.log('error');}})console.log(ajax); // 返回一个xhr对象
// 链式操作var ajax = $.ajax('data.json');ajax.done(function(){console.log('success1');}).fail(function(){console.log('error');}).done(function(){console.log()})console.log(ajax); // 返回一个deferred对象
// 给出一段非常简单的异步操作代码,使用setTimeout函数var wait = function(){var task = function(){console.log('执行完成)}setTimeout(task, 2000);}wait();
新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤
function waitHandle(){var dtd = $.Deferred(); // 创建一个deferred对象var wait = function(dtd){ // 要求传入一个deferred对象var task = function(){console.log('执行完成');dtd.resolve(); // 表示异步任务已经完成// dtd.reject(); // 表示异步任务失败或出错}setTimeout(task, 2000);return dtd; // 要求返回deferred对象}// 注意,这里一定要有返回值return wait(dtd);}var w = waitHandle();w.then(function(){console.log('ok 1');}, function(){console.log('err 1');}).then(function(){console.log('ok 2');}, function(){console.log('err 2');})
当执行dtd.reject()时:
var w = waitHandle();w.then(function(){console.log('ok 1');}, function(){console.log('err 1');})// 不能链式w.then(function(){console.log('ok 2');}, function(){console.log('err 2');})
上面封装的waitHandle方法,由于直接返回了dtd(deferred对象),所以用户可以直接调用w.reject()方法,导致无论是成功还是失败,最后都走失败。
// 修改function waitHandle(){var dtd = $.Deferred();var wait = function(dtd){var task = function(){console.log('执行完成');dtd.resolve();}setTimeout(task, 2000);return dtd.promise(); // 注意这里返回的是promise,而不是直接返回deferred对象}return wait(dtd);}
ES6的Promise:点这里
// promise封装一个异步加载图片的方法function loadImg(src) {var promise = new Promise(function(resolve,reject){var img = document.createElement('img');img.onload = function(){resolve(img)}img.onerror = function(){reject('图片加载失败')}img.src = src;})return promise;}
这是ES7提案中的,现在babel已经开始支持了,koa也是用async/await实现的。
// 伪代码const load = async function(){const result1 = await loadImg(src1);console.log(result1);const result2 = await loadImg(src2);console.log(result2);}load();
页面渲染过程:

<ul id="list"><li class="item">Item 1</li><li class="item">Item 2</li></ul>
// js模拟虚拟DOM{tag: 'ul',attrs:{id: 'list'},children:[{tag: 'li',attrs: {className: 'item'},children: ['Item 1']},{tag: 'li',attrs: {className: 'item'},children: ['Item 2']}]}
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title><script src="https://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script></head><body><div id="container"></div><button id="btn-change">change</button><script>var data = [{name: '张三',age: '20',address: '北京'},{name: '王五',age: '22',address: '成都'},{name: '李四',age: '21',address: '上海'}]// 渲染函数function render(data) {var $container = $('#container');// 清空容器,重要!!!$container.html('');// 拼接 tablevar $table = $('<table>');$table.append($('<tr><td>name</td><td>age</td><td>address</td>/tr>'));data.forEach(function (item) {$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td>/tr>'))});// 渲染到页面$container.append($table);}$('#btn-change').click(function () {data[1].age = 30;data[2].address = '深圳';// re-render 再次渲染render(data);})// 页面加载完立刻执行(初次渲染)render(data);</script></body></html>
虽然只改变了两个数据,但是整个table都闪烁了(回流&重绘)

官网例子:
var snabbdom = require('snabbdom');var patch = snabbdom.init([ // Init patch function with chosen modulesrequire('snabbdom/modules/class').default, // makes it easy to toggle classesrequire('snabbdom/modules/props').default, // for setting properties on DOM elementsrequire('snabbdom/modules/style').default, // handles styling on elements with support for animationsrequire('snabbdom/modules/eventlisteners').default, // attaches event listeners]);var h = require('snabbdom/h').default; // helper function for creating vnodesvar container = document.getElementById('container');// h函数生成一个虚拟节点var vnode = h('div#container.two.classes', {on: {click: someFn}}, [h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),' and this is just normal text',h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')]);// Patch into empty DOM element – this modifies the DOM as a side effectpatch(container, vnode); // 把vnode加入到container中// 数据改变,重新生成一个newVnodevar newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),' and this is still just normal text',h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')]);// Second `patch` invocation// 将newVnode更新到之前的vnode中,从而更新视图patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
var vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')]){tag: 'ul',attrs:{id: 'list'},children:[{tag: 'li',attrs: {className: 'item'},children: ['Item 1']},{tag: 'li',attrs: {className: 'item'},children: ['Item 2']}]}
var vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')])var container = document.getElementById('container');patch(container, vnode);// 模拟改变var btnChange = document.getElementById('btn-change');btnChange.addEventListener('click',function(){var newVnode = h('ul#list',{},[h('li.item',{},'Item 111'),h('li.item',{},'Item 222'),h('li.item',{},'Item 333')])patch(vnode, newVnode);})
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script></head><body><div id="container"></div><button id="btn-change">change</button><script>var snabbdom = window.snabbdom;// 定义 patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners])// 定义 hvar h = snabbdom.h;var container = document.getElementById('container');// 生成 vnodevar vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')])patch(container, vnode);// 模拟数据改变var btnChange = document.getElementById('btn-change');btnChange.addEventListener('click',function(){var newVnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 222'),h('li.item',{},'Item 333')])patch(vnode, newVnode);})</script></body></html>
看图,只有修改了的数据才进行了刷新,减少了DOM操作,这其实就是vnode与newVnode对比,找出改变了的地方,然后只重新渲染改变的

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title></head><body><div id="container"></div><button id="btn-change">change</button><script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script><script type="text/javascript">var snabbdom = window.snabbdom;// 定义关键函数 patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners]);// 定义关键函数 hvar h = snabbdom.h;// 原始数据var data = [{name: '张三',age: '20',address: '北京'},{name: '王五',age: '22',address: '成都'},{name: '李四',age: '21',address: '上海'}]// 把表头也放在 data 中data.unshift({name: '姓名',age: '年龄',address: '地址'});var container = document.getElementById('container')// 渲染函数var vnode;function render(data) {var newVnode = h('table', {}, data.map(function (item) {var tds = [];var i;for (i in item) {if (item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i] + ''));}}return h('tr', {}, tds)}));if (vnode) {// re-renderpatch(vnode, newVnode);} else {// 初次渲染patch(container, newVnode);}// 存储当前的 vnode 结果vnode = newVnode;}// 初次渲染render(data)var btnChange = document.getElementById('btn-change')btnChange.addEventListener('click', function () {data[1].age = 30data[2].address = '深圳'// re-renderrender(data)})</script></body></html>

这里有两个文本文件:
借用git bash中 diff 命令可以比较两个文件的区别:


虚拟DOM ---> DOM
// 一个实现流程,实际情况还很复杂function createElement(vnode) {var tag = vnode.tag // 'ul'var attrs = vnode.attrs || {}var children = vnode.children || []if (!tag) {return null}// 创建真实的 DOM 元素var elem = document.createElement(tag)// 属性var attrNamefor (attrName in attrs) {if (attrs.hasOwnProperty(attrName)) {// 给 elem 添加属性elem.setAttribute(attrName, attrs[attrName])}}// 子元素children.forEach(function (childVnode) {// 给 elem 添加子元素elem.appendChild(createElement(childVnode)) // 递归})// 返回真实的 DOM 元素return elem}
vnode ---> newVnode
function updateChildren(vnode, newVnode) {var children = vnode.children || [];var newChildren = newVnode.children || [];children.forEach(function (childVnode, index) {var newChildVnode = newChildren[index];if (childVnode.tag === newChildVnode.tag) {// 深层次对比,递归updateChildren(childVnode, newChildVnode);} else {// 替换replaceNode(childVnode, newChildVnode);}})}function replaceNode(vnode, newVnode) {var elem = vnode.elem; // 真实的 DOM 节点var newElem = createElement(newVnode);// 替换}
<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.2.1/jquery.min.js"></script><script type="text/javascript">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>
<div id="app"><div><input v-model="title"><button v-on:click="add">submit</button></div><div><ul><li v-for="item in list">{{item}}</li></ul></div></div><script type="text/javascript">// data 独立var data = {title: '',list: []}// 初始化 Vue 实例var vm = new Vue({el: '#app',data: data,methods: {add: function () {this.list.push(this.title);this.title = '';}}})</script>
数据和视图的分离,解耦(开放封闭原则,对扩展开放,对修改封闭)
在jQuery中在jQuery代码中操作视图和数据,混在一块了
以数据驱动视图,只关心数据变化,DOM操作被封装
只改数据,视图自动更新
MVC (Model View Controller)


MVVM (Model View ViewModel)
View 通过
事件绑定(DOM Listeners) 操作Model; Model通过数据绑定(Data Bindings)操作View。
var vm = new Vue({el: '#app',data: {name: 'zhangsan',age: 20}})// vm.name = 'zhangsan'// vm.age = '20'
var obj = {name: 'zhangsan',age: 25}console.log(obj.name); // 获取属性的时候,如何监听obj.age = 26; // 赋值属性的时候,如何监听
上面是无法监听对象的属性的访问以及赋值操作的,直接就产生了操作的结果。
var obj = {}var _name = 'shangsan'Object.defineProperty(obj, 'name', {get: function () {console.log('get', _name) // 监听return _name},set: function (newVal) {console.log('set', newVal) // 监听_name = newVal}})console.log(obj.name); // 可以监听到obj.name = 'lisi'; // 可以监听到
<div id="app"><div><input v-model="title"><button v-on:click="add">submit</button></div><div><ul><li v-for="item in list">{{item}}</li></ul></div></div>
v-if v-for 等v-if v-for 等),必须用JS才能实现(图灵完备)
var obj = {name: 'zhangsan',age: 20,getAddress: function () {alert('beijing')}}// 不使用withfunction fn() {alert(obj.name)alert(obj.age)obj.getAddress()}fn()// 使用withfunction fn1() {with(obj) {alert(age)alert(name)getAddress()}}fn1()
<div id="app"><p>{{price}}</p></div><script>var vm = new Vue({el: '#app',data: {price: 100}})</script>
模板将变成下面这个样子:
function render() {with(this) { // this 就是 vmreturn _c('div',{attrs: {'id': 'app'}},[_c('p', [_v(_s(price))])])}}

在vue源码里alert render 函数
以上面vue实现的todolist为例:
with(this){ // this 就是 vmreturn _c( // _c创建一个标签'div',{attrs:{"id":"app"}},[_c('div',[_c('input',{directives:[{name:"model",rawName:"v-model",value:(title),expression:"title"}],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))])}) // _l 解析 v-for 循环)])])}



// Input 组件import React, { Component } from 'react'class Input extends Component {constructor(props) {super(props);this.state = {title: ''}}render() {return (<div><input value={this.state.title} onChange={this.changeHandle.bind(this)}/><button onClick={this.clickHandle.bind(this)}>submit</button></div>)}changeHandle(event) {this.setState({title: event.target.value})}clickHandle() {const title = this.state.title;const addTitle = this.props.addTitle;addTitle(title); // 重点!!!this.setState({title: ''})}}export default Input;
// list 组件import React, { Component } from 'react'class List extends Component {constructor(props) {super(props);}render() {const list = this.props.data;return (<ul>{list.map((item, index) => {return <li key={index}>{item}</li>})}</ul>)}}export default List;
// todo 页面import React, { Component } from 'react'import Input from './input/index.js'import List from './list/index.js'class Todo extends Component {constructor(props) {super(props);this.state = {list: ['a', 'b']}}render() {return (<div><Input addTitle={this.addTitle.bind(this)}/><List data={this.state.list}/></div>)}addTitle(title) {const currentList = this.state.listthis.setState({list: currentList.concat(title)})}}export default Todo;


ReactDOM.render(<div><h1>{1+1}</h1></div>,document.getElementById('example'));ReactDOM.render(<div><h1>{i == 1 ? 'True!' : 'False'}</h1></div>,document.getElementById('example'));
var myStyle = {fontSize: 100,color: '#FF0000'};ReactDOM.render(<div><h1 style = {myStyle}>JSX样式</h1><p style={{color:'red',fontSize:'20px'}}>内联样式</p><div className="class">类名</div></div>,document.getElementById('example'));
ReactDOM.render(<div><h1>JSX语法</h1>{/*注释...*/}</div>,document.getElementById('example'));
ReactDOM.render(const list = [1,2,3,4,5]<div><ul>list.map((item, index) => {return <li key={index}>{item}</li>})</ul></div>,document.getElementById('example'));

setState 为何需要异步
hybrid 即前端和客服端的混合开发
WebView是一个专门用来显示网页的View子类。它使用WebKit渲染引擎来显示网页,并且支持包括前进,后退,放大,缩小,文本搜索等多种功能。
File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样。