@Bios
2019-06-28T06:51:02.000000Z
字数 27067
阅读 1032
js
这是一个系列记录,也有自身的理解。只为了提高自己,坚持写完
分为8个大的部分:
现在基本上开发中都在使用ES6,浏览器环境支持不好,可以用babel插件来解决。
采用‘二八定律’,主要涉及ES6常用且重要的部分。
// util1.js
export default {
a : 100
}
const str = 'hello';
export default str;
// export default const str = 'hello'; X
// util2.js
export function fn1() {
console.log('fn1');
}
export function fn2() {
console.log('fn2');
}
export const fn3 = ()=> {
console.log('fn3');
}
// index.js
import 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
而不是 exports
Node
里面的模块系统遵循的是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.js
function sayHello() {
console.log('hello');
}
module.exports = sayHello;
// app.js
var sayHello = require('./sayHello');
sayHello();
定义一个sayHello模块,模块里定义了一个sayHello方法,通过替换当前模块exports对象的方式将sayHello方法导出。
在app.js中加载这个模块,得到的是一个函数,调用该函数,控制台打印hello。
// sayWorld.js
module.exports = function(){
console.log('world');
}
// app.js
var sayWorld = require('./sayWorld'); // 匿名替换
sayWorld();
当要导出多个变量怎么办呢?这个时候替换当前模块对象的方法就不实用了,我们需要用到exports对象。
// userExports.js
exports.a = function () {
console.log('a exports');
}
exports.b = function () {
console.log('b exports');
}
// app.js
var useExports = require('./userExports');
useExports.a();
useExports.b();
// a exports
// b exports
当然,将useExports.js改成这样也是可以的:
// userExports.js
module.exports.a = function () {
console.log('a exports');
}
module.exports.b = function () {
console.log('b exports');
}
在实际开发当中可以只使用
module.exports
避免造成不必要的问题。
// .babelrc
{
"presets": ["es2015","latest"],
"plugins": []
}
npm init
rollup.js
需要的一些插件 npm i rollup rollup-plugin-node-resolve rollup-plugin-babel babel-core babel-plugin-external-helpers babel-preset-latest --save-dev
rollup 功能单一(打包js模块化), webpack功能强大
工具尽量功能单一,可继承,可扩展
// .babelrc
{
"presets":[
["latest", {
"es2015":{
"modules": false
}
}]
],
"plugins":["external-helpers"]
}
// rollup.config.js
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
export default {
entry: 'src/index.js',
format: 'umd',
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
})
],
dest: 'build/bundle.js'
}
// 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}
let
const
与var
都是用来定义变量的,不同的是let
自带作用域,const
不能重复赋值。
let name = 'FinGet'
while (true) {
let name = 'GetFin'
console.log(name) //GetFin
break
}
console.log(name) //FinGet
let
定义的变量只在包含它的代码块内有用
const PI = 3.1415926;
PI = 3.14; // 错误
let name = 'FinGet';
let age = 22;
// js
var str = '我是'+ name+',今年'+age+'岁'; // 很麻烦
let str1 = `我是${name},今年${age}岁`; // 简单多了
模板字符串就是用
`(Tab键上面那个)包含,变量就是用
${}`表示
let obj = {
name: 'FinGet',
age: 22,
job: '前端',
addr: '成都'
}
let {name,age} = obj;
console.log(name); // FinGet
console.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
带来的不合理场景就是用来计数的循环变量泄露为全局变量,看下面的例子:
// js
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
let 自带块级作用域
// ES6
var 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) // Kevin
console.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) // true
console.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) // Daisy
delete 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('');
// 拼接 table
var $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 modules
require('snabbdom/modules/class').default, // makes it easy to toggle classes
require('snabbdom/modules/props').default, // for setting properties on DOM elements
require('snabbdom/modules/style').default, // handles styling on elements with support for animations
require('snabbdom/modules/eventlisteners').default, // attaches event listeners
]);
var h = require('snabbdom/h').default; // helper function for creating vnodes
var 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 effect
patch(container, vnode); // 把vnode加入到container中
// 数据改变,重新生成一个newVnode
var 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;
// 定义 patch
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
// 定义 h
var h = snabbdom.h;
var container = document.getElementById('container');
// 生成 vnode
var 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;
// 定义关键函数 patch
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
// 定义关键函数 h
var 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-render
patch(vnode, newVnode);
} else {
// 初次渲染
patch(container, newVnode);
}
// 存储当前的 vnode 结果
vnode = newVnode;
}
// 初次渲染
render(data)
var btnChange = document.getElementById('btn-change')
btnChange.addEventListener('click', function () {
data[1].age = 30
data[2].address = '深圳'
// re-render
render(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 attrName
for (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')
}
}
// 不使用with
function fn() {
alert(obj.name)
alert(obj.age)
obj.getAddress()
}
fn()
// 使用with
function 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 就是 vm
return _c(
'div',
{
attrs: {'id': 'app'}
},
[
_c('p', [_v(_s(price))])
]
)
}
}
在vue源码里alert
render
函数
以上面vue实现的todolist为例:
with(this){ // this 就是 vm
return _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.list
this.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资源管理器中打开文件一样。