GithubHelp home page GithubHelp logo

rainzhai / rainzhai.github.com Goto Github PK

View Code? Open in Web Editor NEW
2.0 2.0 0.0 36.13 MB

宅鱼

Home Page: http://rainzhai.github.io

License: Apache License 2.0

HTML 85.24% CSS 3.36% JavaScript 11.36% PHP 0.05%
blog html javascript css fe

rainzhai.github.com's Introduction

rainzhai.github.com

rainzhai.github.com's People

Contributors

rainzhai avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

rainzhai.github.com's Issues

数组最小最大数的最优算法是什么?

如果需要同时找出最小和最大数,可以采用”逐对“处理的方法。维护min和max两个变量,每次读进两个数,比较它们的大小,小的放进min, 大的放进max。这样每次循环需要3次比较,经历n/2个循环,一共约有1.5n次比较.。相比之下,分别求最大最小数的方法需要2n - 2次比较,理论上不如以上方法效率高。

前端工具与方案(web抓包 增量更新 webp)

1. web抓包替换调试-把静态资源指向到本地

  • LivePool 是一个基于 NodeJS,类似 Fiddler 能够支持抓包和本地替换的 Web 开发调试工具 https://github.com/rehorn/livepool
  • 使用 Fiddler 的 AutoResponder 的功能。
  • 使用模块加载器提供的 debug 功能来做线上资源到本地资源的映射,例如 seajs-debug
  • 自定义 DNS xdns

2. 前端资源增量更新

3. png转为webp

4.常用视频播放器

5.javascript在线运行

https://jsbin.com/?html,js,output
http://codepen.io
https://jsfiddle.net

javascript事件机制同步异步和事件循环(Event Loop)

运行时概念 Runtime

可视化描述

event loop

函数调用形成了一个frames的栈。

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

调用g的时候,创建了第一个frame,包含了g的参数和局部变量。当 g 调用 f 的时候,第二个frame就被创建、并置于第一个frame之上,包含了f的参数和局部变量。当f返回时,最上层的frame就出栈了(剩下g函数调用的frame)。当g返回的时候,栈就空了。

对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。

队列

一个JavaScript运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因此而创建的一个初始栈结构)。当栈再次为空的时候,也就意味着消息处理结束。

单线程

Javascript是单线程的,在JS引擎中负责解释和执行JavaScript代码的线程只有一个(暂称为主线程)。

浏览器不是单线程的

虽然JS运行在浏览器中,是单线程的,每个window一个JS线程,但浏览器不是单线程的其可能有如下线程:

  • javascript引擎线程
  • 界面渲染线程
  • 浏览器事件触发线程
  • Http请求线程

另外Event loop事件队列有单独的线程去处理(暂称为事件队列工作线程)。

同步和异步

如果在函数执行完成的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。
如果在函数执行完成的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。
总结一下,一个异步过程通常是这样的:

主线程发起一个异步请求,相应的事件队列工作线程接收请求并告知主线程已收到(异步函数返回);
主线程可以继续执行后面的代码,同时事件队列工作线程执行异步任务;事件队列工作线程完成工作后,通知主线程;
主线程收到通知后,执行一定的动作(调用回调函数)。

事件循环 Event Loop

类似如下的方式来实现:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

如果当前没有任何消息,queue.waitForMessage 会同步等待消息到来。

"执行至完成"

每一个消息执行完成后,其它消息才会被执行。当一个函数运行时,它不能被取代且会在后续代码运行前先完成(而且能够修改这个函数控制的数据)。这点与C语言不同。例如,C语言中当一个程序在一个线程中运行时,它可以在任何点停止且可以在其它线程中运行其它代码。

这个模型的一个缺点在于当一个消息的完成耗时过长,网络应用无法处理用户的交互如点击或者滚动。浏览器用“程序需要过长时间运行”的对话框来缓解这个问题。一个比较好的解决方案是使消息处理变短。如果可能的话,将一个消息拆分成几个消息。

添加消息

在浏览器里,当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。所以点击一个附带点击事件处理函数的元素会添加一个消息。其它事件亦然。

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间。

零延迟

零延迟(Zero delay) 并不是意味着回调会立即执行。在零延迟调用setTimeout时,其并不是过了给定的时间间隔后就马上执行回调函数。其等待的时间基于队列里正在等待的消息数量。在下面的例子中,"this is just a message"在将会在回调(callback)获得处理之前输出到控制台,这是因为延迟是要求运行时(runtime)处理请求所需的最小时间,但不是确定的时间。

(function () {
  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the  end');
})();

面试题:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(>5s)后,再点击两下,整个过程的输出结果?

setTimeout(function(){
	for(var i = 0; i < 100000000; i++){}
	console.log('timer a');
}, 0)

for(var j = 0; j < 5; j++){
	console.log(j);
}

setTimeout(function(){
	console.log('timer b');
}, 0)

function waitFiveSeconds(){
	var now = (new Date()).getTime();
	while(((new Date()).getTime() - now) < 5000){}
	console.log('finished waiting');
}
//事件监听在js 解析阶段已经生效
document.addEventListener('click', function(){
	console.log('click');
})

console.log('click begin');
waitFiveSeconds();

几个运行时(Runtime)互相通信

一个web worker或者一个跨域的iframe都有它们自己的栈,堆和消息队列。两个不同的运行时只有通过postMessage方法进行通信。这个方法会给另一个运行时添加一个消息如果后者监听了message事件。

绝不阻塞

一个很有趣的事件循环(event loop)模型特性在于,Javascript跟其它语言不同,它永不阻塞。处理I/O (input/output)通常由事件或者回调函数进行实现。所以当一个应用正等待IndexedDB的查询的返回或者一个XHR(译者注:用于Ajax)的请求返回时,它仍然可以处理其它事情例如用户输入。

例外是存在的,如alert或者同步XHR,但避免以它们为最佳实践。

相关链接

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
https://segmentfault.com/a/1190000004322358
http://latentflip.com

promise与async await的关系

函数中返回promise对象可以使用async await语法调用

function go(a){
  return new Promise((resove,reject)=>{
    setTimeout(function(){
      resove(a)
    },3000)
  })
}

async function show(){
  const a = await go('www');
  console.log(a);
}

show()

Virtual dom实现

对一个virtual dom实现的代码做的整理,原文见:livoras/blog#13
1.模拟dom树
element.js

function Element (tagName, props, children) {
  this.tagName = tagName
  this.props = props
  this.children = children
}

Element.prototype.render = function () {
  var el = document.createElement(this.tagName) // 根据tagName构建
  var props = this.props

  for (var propName in props) { // 设置节点的DOM属性
    var propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  var children = this.children || []

  children.forEach(function (child) {
    var childEl = (child instanceof Element)
      ? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
      : document.createTextNode(child) // 如果字符串,只构建文本节点
    el.appendChild(childEl)
  })

  return el
}

module.exports = function (tagName, props, children) {
  return new Element(tagName, props, children)
}

使用:
var ul = el('ul', {id: 'list'}, [
el('li', {class: 'item'}, ['Item 1']),
el('li', {class: 'item'}, ['Item 2']),
el('li', {class: 'item'}, ['Item 3'])
])
var ulRoot = ul.render()
document.body.appendChild(ulRoot)
生成的dom结果如下:
`

  • Item 1
  • Item 2
  • Item 3
`

虚拟dom树差异比对
每一层进行对比:复杂度O(n)
diff.js

var _ = require('./util')
var patch = require('./patch')
var listDiff = require('list-diff2')

function diff (oldTree, newTree) {
  var index = 0
  var patches = {}
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}

function dfsWalk (oldNode, newNode, index, patches) {
  var currentPatch = []

  // Node is removed.
  if (newNode === null) {
    // Real DOM node will be removed when perform reordering, so has no needs to do anthings in here
  // TextNode content replacing
  } else if (_.isString(oldNode) && _.isString(newNode)) {
    if (newNode !== oldNode) {
      currentPatch.push({ type: patch.TEXT, content: newNode })
    }
  // Nodes are the same, diff old node's props and children
  } else if (
      oldNode.tagName === newNode.tagName &&
      oldNode.key === newNode.key
    ) {
    // Diff props
    var propsPatches = diffProps(oldNode, newNode)
    if (propsPatches) {
      currentPatch.push({ type: patch.PROPS, props: propsPatches })
    }
    // Diff children. If the node has a `ignore` property, do not diff children
    if (!isIgnoreChildren(newNode)) {
      diffChildren(
        oldNode.children,
        newNode.children,
        index,
        patches,
        currentPatch
      )
    }
  // Nodes are not the same, replace the old node with new node
  } else {
    currentPatch.push({ type: patch.REPLACE, node: newNode })
  }

  if (currentPatch.length) {
    patches[index] = currentPatch
  }
}

function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {
  var diffs = listDiff(oldChildren, newChildren, 'key')
  newChildren = diffs.children

  if (diffs.moves.length) {
    var reorderPatch = { type: patch.REORDER, moves: diffs.moves }
    currentPatch.push(reorderPatch)
  }

  var leftNode = null
  var currentNodeIndex = index
  _.each(oldChildren, function (child, i) {
    var newChild = newChildren[i]
    currentNodeIndex = (leftNode && leftNode.count)
      ? currentNodeIndex + leftNode.count + 1
      : currentNodeIndex + 1
    dfsWalk(child, newChild, currentNodeIndex, patches)
    leftNode = child
  })
}

function diffProps (oldNode, newNode) {
  var count = 0
  var oldProps = oldNode.props
  var newProps = newNode.props

  var key, value
  var propsPatches = {}

  // Find out different properties
  for (key in oldProps) {
    value = oldProps[key]
    if (newProps[key] !== value) {
      count++
      propsPatches[key] = newProps[key]
    }
  }

  // Find out new property
  for (key in newProps) {
    value = newProps[key]
    if (!oldProps.hasOwnProperty(key)) {
      count++
      propsPatches[key] = newProps[key]
    }
  }

  // If properties all are identical
  if (count === 0) {
    return null
  }

  return propsPatches
}

function isIgnoreChildren (node) {
  return (node.props && node.props.hasOwnProperty('ignore'))
}

module.exports = diff

3.差异应用到真正的DOM树
patch.js

var _ = require('./util')

var REPLACE = 0
var REORDER = 1
var PROPS = 2
var TEXT = 3

function patch (node, patches) {
  var walker = {index: 0}
  dfsWalk(node, walker, patches)
}

function dfsWalk (node, walker, patches) {
  var currentPatches = patches[walker.index]

  var len = node.childNodes
    ? node.childNodes.length
    : 0
  for (var i = 0; i < len; i++) {
    var child = node.childNodes[i]
    walker.index++
    dfsWalk(child, walker, patches)
  }

  if (currentPatches) {
    applyPatches(node, currentPatches)
  }
}

function applyPatches (node, currentPatches) {
  _.each(currentPatches, function (currentPatch) {
    switch (currentPatch.type) {
      case REPLACE:
        var newNode = (typeof currentPatch.node === 'string')
          ? document.createTextNode(currentPatch.node)
          : currentPatch.node.render()
        node.parentNode.replaceChild(newNode, node)
        break
      case REORDER:
        reorderChildren(node, currentPatch.moves)
        break
      case PROPS:
        setProps(node, currentPatch.props)
        break
      case TEXT:
        if (node.textContent) {
          node.textContent = currentPatch.content
        } else {
          // fuck ie
          node.nodeValue = currentPatch.content
        }
        break
      default:
        throw new Error('Unknown patch type ' + currentPatch.type)
    }
  })
}

function setProps (node, props) {
  for (var key in props) {
    if (props[key] === void 666) {
      node.removeAttribute(key)
    } else {
      var value = props[key]
      _.setAttr(node, key, value)
    }
  }
}

function reorderChildren (node, moves) {
  var staticNodeList = _.toArray(node.childNodes)
  var maps = {}

  _.each(staticNodeList, function (node) {
    if (node.nodeType === 1) {
      var key = node.getAttribute('key')
      if (key) {
        maps[key] = node
      }
    }
  })

  _.each(moves, function (move) {
    var index = move.index
    if (move.type === 0) { // remove item
      if (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for inserting
        node.removeChild(node.childNodes[index])
      }
      staticNodeList.splice(index, 1)
    } else if (move.type === 1) { // insert item
      var insertNode = maps[move.item.key]
        ? maps[move.item.key].cloneNode(true) // reuse old item
        : (typeof move.item === 'object')
            ? move.item.render()
            : document.createTextNode(move.item)
      staticNodeList.splice(index, 0, insertNode)
      node.insertBefore(insertNode, node.childNodes[index] || null)
    }
  })
}

patch.REPLACE = REPLACE
patch.REORDER = REORDER
patch.PROPS = PROPS
patch.TEXT = TEXT

module.exports = patch

[‘1’, ‘7’, ‘11’].map(parseInt) 后,得到的结果是 [1, NaN, 3] ,而不是 [1, 7, 11]

['1', '7', '11'].map(parseInt); => [1, NaN, 3] 
//  第一次第一次迭代 index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']); => 1

// 第二次迭代 index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']); => NaN

// 第三次迭代, index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']); => 3

[‘1’, ‘7’, ‘11’].map(parseInt) 不能按预期工作,是因为 map 在每次迭代时都会将三个参数传递到 parseInt() 中。第二个参数 index 作为 radix 参数传递给 parseInt。因此,数组中的每个字符串都使用不同的基数进行解析。‘7’ 按照基数 1 进行解析,即 NaN;‘11’ 按照基数 2 进行解析,即 3。‘1’ 按照默认基数 10 进行解析,因为它的索引 0 是假。

因此,下面的代码能按预期工作:
['1', '7', '11'].map(numStr => parseInt(numStr));

javascript模块化

http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html
模块,又称构件,是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。它具有两个基本的特征:外部特征和内部特征。外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能;内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码)。
我最喜欢的ES6 模块功能的特性是,导入是实时只读的。(CommonJS 只是相当于把导出的代码复制过来)。

什么是模块
一般来讲,模块是一个独立的JavaScript文件。模块文件可以包含一个类定义、一组相关的类、一个实用函数库的代码。
为什么要使用模块
模块化可以使你的代码低耦合,功能模块直接不相互影响。我个人认为模块化主要有以下几点好处:

1.可维护性:根据定义,每个模块都是独立的。良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护。

2.命名空间:在JavaScript中,最高级别的函数外定义的变量都是全局变量(这意味着所有人都可以访问到它们)。也正因如此,当一些无关的代码碰巧使用到同名变量的时候,我们就会遇到“命名空间污染”的问题。

3.可复用性

如何引入模块
模块模式
模块模式一般用来模拟类的概念(因为原生JavaScript并不支持类,虽然最新的ES6里引入了Class不过还不普及)这样我们就能把公有和私有方法还有变量存储在一个对象中。这样我们就能在公开调用API的同时,仍然在一个闭包范围内封装私有变量和方法。

实现模块模式的方法有很多种,下面的例子是通过匿名闭包函数的方法。(在JavaScript中,函数是创建作用域的唯一方式。)

例1:匿名闭包函数

(function () {
  // 在函数的作用域中下面的变量是私有的
 var temp= 1223;
 function(){
   temp2 = 45+temp;
 }
}());

通过这种构造,我们的匿名函数有了自己的作用域或“闭包”。 这允许我们从父(全局)命名空间隐藏变量。
这种方法的好处在于,你可以在函数内部使用局部变量,而不会意外覆盖同名全局变量,但仍然能够访问到全局变量。

例2:全局引入

另一种比较受欢迎的方法是一些诸如jQuery的库使用的全局引入。和我们刚才举例的匿名闭包函数很相似,只是传入全局变量的方法不同:

(function (globalVariable) {
  globalVariable.filter = function(collection, test) {
     ...
  }; 
 }(globalVariable));

在这个例子中,globalVariable 是唯一的全局变量。这种方法的好处是可以预先声明好全局变量,让你的代码更加清晰可读。

例3:对象接口

像下面这样,还有一种创建模块的方法是使用独立的对象接口:

var myObj= (function () {
  // 在函数的作用域中下面的变量是私有的
  var myList= [93, 95];

  // 通过接口在外部访问下列方法
  // 与此同时这些方法又都在函数内部
  return {
    average: function() {
      var total = myList.reduce(function(accumulator, item) {
        return accumulator + item;
        }, 0);
      return total / myGrades.length;
    }
  }
})();
myGradesCalculate.average();

例4:揭示模块模式 Revealing module pattern
这和我们之前的实现方法非常相近,除了它会确保,在所有的变量和方法暴露之前都会保持私有.

CommonJS

CommonJS 扩展了JavaScript声明模块的API.

CommonJS模块可以很方便得将某个对象导出,让他们能够被其他模块通过 require 语句来引入。要是你写过 Node.js 应该很熟悉这些语法。

通过CommonJS,每个JS文件独立地存储它模块的内容(就像一个被括起来的闭包一样)。在这种作用域中,我们通过 module.exports 语句来导出对象为模块,再通过 require 语句来引入。

直观的例子:

function myModule() {
  this.hello = function() {
    return 'hello!';
  } 
}
module.exports = myModule;

通过指定导出的对象名称,CommonJS模块系统可以识别在其他文件引入这个模块时应该如何解释。

然后在某个人想要调用 myMoudle 的时候,只需要 require 一下:

var myModule = require('myModule');
var myModuleInstance = new myModule();
myModuleInstance.hello(); // 'hello!' 

这种实现比起模块模式有两点好处:

  • 避免全局命名空间污染
  • 明确代码之间的依赖关系

需要注意的一点是,CommonJS以服务器优先的方式来同步载入模块,假使我们引入三个模块的话,他们会一个个地被载入。

它在服务器端用起来很爽,可是在浏览器里就不会那么高效了。毕竟读取网络的文件要比本地耗费更多时间。只要它还在读取模块,浏览器载入的页面就会一直卡着不动。

AMD
CommonJS已经挺不错了,但假使我们想要实现异步加载模块该怎么办?答案就是Asynchronous Module Definition(异步模块定义规范),简称AMD.

通过AMD载入模块的代码一般这么写:

define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
  console.log(myModule.hello());
});

这里我们使用 define 方法,第一个参数是依赖的模块,这些模块都会在后台无阻塞地加载,第二个参数则作为加载完毕的回调函数。

回调函数将会使用载入的模块作为参数。在这个例子里就是 myMoudle 和 myOtherModule.最后,这些模块本身也需要通过 define 关键词来定义。

拿 myModule 来举个例子:

define([], function() {
  return {
    hello: function() {
      console.log('hello');
    }
  };
});

重申一下,不像CommonJS,AMD是优先浏览器的一种异步载入模块的解决方案。

除了异步加载以外,AMD的另一个优点是你可以在模块里使用对象、函数、构造函数、字符串、JSON或者别的数据类型,而CommonJS只支持对象。

再补充一点,AMD不支持Node里的一些诸如 IO,文件系统等其他服务器端的功能。另外语法上写起来也比CommonJS麻烦一些。

UMD

HTML5核心特性

HTML5核心内容介绍

HTML5是用于取代1999年所制定的 HTML 4.01 和 XHTML 1.0 标准的 HTML [1](标准通用标记语言下的一个应用)标准版本;现在仍处于发展阶段,但大部分浏览器已经支持某些 HTML5 技术。HTML 5有八大特点:

语义特性(Class:Semantic)

HTML5赋予网页更好的意义和结构。更加丰富的标签。

<header>
    <hgroup>
        <h1>网站标题</h1>
        <h1>网站副标题</h1>
    </hgroup>
</header>
<nav>
    <ul>
        <li>HTML 5</li>
        <li>CSS3</li>
        <li>JavaScript</li>
    </ul>
</nav>
<article>
    <p>内容</p>
    <section></section>
    <aside>
        <h1>标题</h1>
        <p>内容</p>
	<figure class="effect-julia">
	  <img src="img/21.jpg" alt="img21"/>
	  <figcaption>
	    <h2>head</h2>
	    <div>
	      <p>this is head intro</p>
	    </div>
	    <a href="#">View more</a>
	  </figcaption>            
	</figure>
    </aside>
</article>
<footer>
    COPYRIGHT
</footer>

本地存储特性(Class: OFFLINE & STORAGE)

基于HTML5开发的网页APP拥有更短的启动时间,更快的联网速度,这些全得益于HTML5 APP Cache,以及本地存储功能。Indexed DB(html5本地存储最重要的技术之一)。
localStorage - 没有时间限制的数据存储
sessionStorage - 针对一个 session 的数据存储

  用法:.setItem( key, value)
  sessionStorage.setItem("key", "value"); 
  localStorage.setItem("site", "js8.in");

  getItem获取value用途:获取指定key本地存储的值
  用法:.getItem(key)
  var value = sessionStorage.getItem("key"); 
  var site = localStorage.getItem("site");

  removeItem删除key用途:删除指定key本地存储的值
  用法:.removeItem(key)
  sessionStorage.removeItem("key"); 
  localStorage.removeItem("site");

  clear清除所有的key/value用途:清除所有的key/value
  用法:.clear()
  sessionStorage.clear(); 
  localStorage.clear();

indexedDB数据库

    //添加数据
    function insertAnObj(indexDbName) {
        var userinfos = [{
            id: 1001,
            name: "小李",
            age: 24
        }];
        var openRequest = indexedDB.open(indexDbName, 1);
        openRequest.onerror = function(e) { //当创建数据库失败时候的回调
            console.log("Database error: " + e.target.errorCode);
        };
        openRequest.onsuccess = function(event) {
            console.log("Database created");
            db = openRequest.result; //创建数据库成功时候,将结果给db,此时db就是当前数据库
            //打开和userinfo相关的objectstore的事务
            var transaction = db.transaction("userinfo", 'readwrite');
            var store = transaction.objectStore("userinfo");
            for (var i = 0; i < userinfos.length; i++) {
                //alert("add"+userinfos[i]);
                store.add(userinfos[i]); //将对象添加至userinfo相关的objectstore中
            }
        };
        openRequest.onupgradeneeded = function(event) {
            var db = event.target.result;
            //在第一次创建数据库的时候,就创建userinfo相关的objectstore,以供后面添加数据时候使用
            if (!db.objectStoreNames.contains('userinfo')) {
                //keyPath:Javascript对象,对象必须有一属性作为键值
                db.createObjectStore('userinfo', {
                    keyPath: "id"
                });
            }

        }
    }
    insertAnObj("hello");

//根据id查找数据
    function findDbdata(indexDbName,value) {
        var openRequest = indexedDB.open(indexDbName);
        var db;
        openRequest.onerror = function(e) {//当创建数据库失败时候的回调
            console.log("Database error: " + e.target.errorCode);
        };
        openRequest.onsuccess = function(event) {
            console.log("Database created");
            db = openRequest.result; //创建数据库成功时候,将结果给db,此时db就是当前数据库
            var transaction = db.transaction("userinfo",'readwrite');
            var objectStore = transaction.objectStore("userinfo");
            //var cursor = objectStore.openCursor();
            var request = objectStore.get(Number(value));//查找i=1的对象,这里使用Number将1转换成数值类型
            request.onsuccess = function(e) {
                var res = e.target.result; //查找成功时候返回的结果对象
                console.dir(res);
                if (res) {
                    for (var field in res) { //遍历每一个对象属性
                        console.log(field+":"+res[field]);
                        // alert(res[field]);
                    };
                };
            }
        };
        openRequest.onupgradeneeded = function (event) {//更改数据库,或者存储对象时候在这里处理

        };
    } 

    findDbdata("hello","1001")

//查找所有数据
    function findAllDbdata(indexDbName) {
        var openRequest = indexedDB.open(indexDbName);
        var db;
        openRequest.onsuccess = function(event) {
            console.log("Database created");
            db = openRequest.result; //创建数据库成功时候,将结果给db,此时db就是当前数据库
            var transaction = db.transaction("userinfo",'readonly');
            var objectStore = transaction.objectStore("userinfo");
            var cursor = objectStore.openCursor();
            cursor.onsuccess = function(e) { 
                var res = e.target.result; 
                if(res) { 
                    console.log("Key", res.key); 
                    var request = objectStore.get(Number(res.key));//根据查找出来的id,再次逐个查找
                    request.onsuccess = function(e) {
                        var res = e.target.result; //查找成功时候返回的结果对象
                        //console.dir(res);
                        if (res) {
                            for (var field in res) { //遍历每一个对象属性
                                console.log(field+":"+res[field]);
                            // alert(res[field]);
                            };
                        };
                    }
                    res.continue(); 
                } 
            }   
        };
    }
    findAllDbdata("hello");

//通过id删除数据
    function deleteDataById(indexDbName,id) {
        var openRequest = indexedDB.open(indexDbName);
        var db;
        openRequest.onsuccess = function(event) {
            db = openRequest.result; //创建数据库成功时候,将结果给db,此时db就是当前数据库
            var transaction = db.transaction("userinfo",'readwrite');
            var objectStore = transaction.objectStore("userinfo");
            var request = objectStore.delete(Number(id));//根据查找出来的id,再次逐个查找
            request.onsuccess = function(e) {
                console.log("delete success");
            }
        }
    }
    deleteDataById("hello","1001");

    //删除所有数据
    function deleteAllData(indexDbName) {
        var openRequest = indexedDB.open(indexDbName);
        var db;
        openRequest.onsuccess = function(event) {
            db = openRequest.result; //创建数据库成功时候,将结果给db,此时db就是当前数据库
            var transaction = db.transaction("userinfo",'readwrite');
            var objectStore = transaction.objectStore("userinfo");
            objectStore.clear();
        }
    }
    deleteAllData("hello");

其他功能索引,游标

设备兼容特性 (Class: DEVICE ACCESS)

从Geolocation功能的API文档公开以来,HTML5为网页应用开发者们提供了更多功能上的优化选择,带来了更多体验功能的优势。HTML5提供了前所未有的数据与应用接入开放接口。使外部应用可以直接与浏览器内部的数据直接相连,例如视频影音可直接与microphones及摄像头相联。

    function getLocation() {
        if (navigator.geolocation) {
            var options = {
                enableHighAccuracy: true,
                timeout: 5000,
                maximumAge: 0
            };

            function success(pos) {
                var crd = pos.coords;

                console.log('Your current position is:');
                console.log(`Latitude : ${crd.latitude}`);
                console.log(`Longitude: ${crd.longitude}`);
                console.log(`More or less ${crd.accuracy} meters.`);
            };

            function error(err) {
                console.log(err.message);
            };

            navigator.geolocation.getCurrentPosition(success, error, options);
        } else {
            console.log("Geolocation is not supported by this browser.")
        }
    }
    getLocation();

连接特性(Class: CONNECTIVITY)

更有效的连接工作效率,使得基于页面的实时聊天,更快速的网页游戏体验,更优化的在线交流得到了实现。HTML5拥有更有效的服务器推送技术,Server-Sent Event和WebSockets就是其中的两个特性,这两个特性能够帮助我们实现服务器将数据“推送”到客户端的功能。
Server-Sent Event 即服务器单向消息传递事件,网页可以自动获取来自服务器的更新。以前也可能做到这一点,前提是网页不得不询问是否有可用的更新。通过服务器发送事件,更新能够自动到达。如:Facebook/Twitter 更新、估价更新、新的博文、赛事结果等。
Server-Sent Event示例
新建basic_sse.html

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Basic SSE Example</title>
</head>
<body>
    <pre id="x">Initializing...</pre>
    <script>
        var es = new EventSource("/sse");
        es.addEventListener("message", function(e) {
            document.getElementById("x").innerHTML += "\n" + e.data;
        }, false);
    </script>
</body>
</html>

新建datepush.js

var http = require("http"),
    fs = require("fs");
var port = parseInt(process.argv[2] || 1234);
http.createServer(function(request, response) {
    console.log("Client connected:" + request.url);
    if (request.url != "/sse") {
        fs.readFile("basic_sse.html", function(err, file) {
            response.writeHead(200, { 'Content-Type': 'text/html' });
            var s = file.toString(); //file is a buffer
            s = s.replace("basic_sse.php", "sse");
            response.end(s);
        });
        return;
    }
    //Below is to handle SSE request. It never returns.
    response.writeHead(200, { "Content-Type": "text/event-stream" });
    var timer = setInterval(function() {
        var content = "data:" + new Date().toISOString() + "\n\n";
        var b = response.write(content);
        if (!b) console.log("Data got queued in memory (content=" + content + ")");
        else console.log("Flushed! (content=" + content + ")");
    }, 1000);
    request.connection.on("close", function() {
        response.end();
        clearInterval(timer);
        console.log("Client closed connection. Aborting.");
    });
}).listen(port);
console.log("Server running at http://localhost:" + port);

运行node datepush.js
然后在浏览器输入:http://localhost:1234/basic_sse.html 可看到通过Server-Sent Event实时获取的消息

WebSocket 是伴随HTML5发布的一种新协议。它实现了浏览器与服务器全双工通信(full-duplex),可以传输基于消息的文本和二进制数据。WebSocket 是浏览器中最靠近套接字的API,除最初建立连接时需要借助于现有的HTTP协议,其他时候直接基于TCP完成通信。它是浏览器中最通用、最灵活的一个传输机制,其极简的API 可以让我们在客户端和服务器之间以数据流的形式实现各种应用数据交换(包括JSON 及自定义的二进制消息格式),而且两端都可以随时向另一端发送数据。
官网: http://www.websocket.org/quantum.html
轮询和websocket对比
Latency comparison between the polling and WebSocket applications

轮询和websocket性能对比
Comparison of the unnecessary network throughput overhead between the polling and the WebSocket applications

WebSockets例子:
在项目目录npm install nodejs-websocket

//新建server.js
var ws = require("nodejs-websocket");
console.log("开始建立连接...")

var game1 = null,game2 = null , game1Ready = false , game2Ready = false;
var server = ws.createServer(function(conn){
    conn.on("text", function (str) {
        console.log("收到的信息为:"+str)
        if(str==="game1"){
            game1 = conn;
            game1Ready = true;
            conn.sendText("success");
        }
        if(str==="game2"){
            game2 = conn;
            game2Ready = true;
        }

        if(game1Ready&&game2Ready){
            game2.sendText(str);
        }

        conn.sendText(str)
    })
    conn.on("close", function (code, reason) {
        console.log("关闭连接")
    });
    conn.on("error", function (code, reason) {
        console.log("异常关闭")
    });
}).listen(8001)
console.log("WebSocket建立完毕")
//新建test.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title> 
</head>
<body>
    <div id="mess"></div>
    <script>
        var mess = document.getElementById("mess");
        if(window.WebSocket){
            var ws = new WebSocket('ws://127.0.0.1:8001');

            ws.onopen = function(e){
                console.log("连接服务器成功");
                ws.send("game2");
            }
            ws.onclose = function(e){
                console.log("服务器关闭");
            }
            ws.onerror = function(){
                console.log("连接出错");
            }

            ws.onmessage = function(e){
                var time = new Date();
                mess.innerHTML+=time+"的消息:"+e.data+"<br>"
            }
        }
    </script>
</body>
</html>

运行node server.js 然后浏览器打开test.html,即可看到运行效果

网页多媒体特性(Class: MULTIMEDIA)

支持网页端的Audio、Video等多媒体功能, 与网站自带的APPS,摄像头,影音功能相得益彰。

三维、图形及特效特性(Class: 3D, Graphics & Effects)

基于SVG、Canvas、WebGL及CSS3的3D功能,用户会惊叹于在浏览器中,所呈现的惊人视觉效果。
SVG动画制作: http://snapsvg.io
SVG数据可视化框架: https://d3js.org
Canvas游戏引擎:egret , cocos2D-js , lufylegend.js 等
WebGL 框架: threejs , Babylon.js等
CSS3 3D: http://rainzhai.github.io/raincss/css3/cube.html

性能与集成特性(Class: Performance & Integration)

没有用户会永远等待你的Loading——HTML5会通过XMLHttpRequest2等技术,帮助您的Web应用和网站在多样化的环境中更快速的工作。

CSS3特性(Class: CSS3)

在不牺牲性能和语义结构的前提下,CSS3中提供了更多的风格和更强的效果。此外,较之以前的Web排版,Web的开放字体格式(WOFF)也提供了更高的灵活性和控制性。
边框

  • border-radius
  • box-shadow
  • border-image

背景(多重背景)

  • background-size
  • background-origin 背景定位
  • background-clip 背景裁剪

文本效果

  • text-align-last 设置如何对齐最后一行或紧挨着强制换行符之前的行
  • text-emphasis 应用重点标记以及重点标记的前景色
  • text-justify "justify" 时所使用的对齐方法
  • text-outline 文本的轮廓
  • text-overflow 文本溢出包含元素时发生的事情
  • text-shadow
  • text-wrap 文本的换行规则
  • word-break 非中日韩文本的换行规则
  • word-wrap 长的不可分割的单词进行分割并换行到下一行

webfont

@font-face{
font-family: myFirstFont;
src: url('Sansation_Light.ttf'),
url('Sansation_Light.eot'); /* IE9+ */
}

2D 转换
transform

  • translate(x,y) 移动
  • rotate(x,y) 转动
  • scale(x,y) 缩放
  • skew(x,y) 倾斜
  • matrix() 矩阵

3D转换

  • rotate3d(x,y,z,angle)
  • translate3d(x,y,z)
    -scale3d(x,y,z)

过渡

  • transition

动画

多列

  • column-count
  • column-gap
  • column-rule

界面
resize
box-sizing
outline-offset

颜色

  • RGBA
  • HSL
  • HSLA

flex布局
media query

优点缺点

网络标准

统一标准,易于开发维护

多设备跨平台

ios, andriod, windows, hybrid app

自适应网页设计

手机端pc端全适配

即时更新

HTML5游戏和应用即时的更新。

总结概括HTML5有以下优点:

1、提高可用性和改进用户的友好体验;
2、有几个新的标签,这将有助于开发人员定义重要的内容;SEO很友好;
3、可以给站点带来更多的多媒体元素(视频和音频),替代FLASH和Silverlight;
6、将被大量应用于移动应用程序和游戏;
7、可移植性好。

缺点:

该标准并未能很好的被浏览器所支持。

Rxjs

    /*     const source = Rx.Observable.fromPromise(new Promise(resolve => resolve(1)));
    //add 10 to the value
    const example = source.map(val => val + 10);
    //output: 11
    const subscribe = example.subscribe(val => console.log(val));*/
    const btn = document.getElementById("btn");
    const input = document.getElementById("inputbox");
    const btnstream = Rx.Observable.fromEvent(btn, "click");
    const inputstream = Rx.Observable.fromEvent(input, "keyup");
    btnstream.subscribe(e => console.log(e.target.value));
    inputstream.subscribe(e => console.log(e), err => console.log(err), () => console.log("完成"));

    /*        const source = Rx.Observable.range(0, 6).map(x => x * x);
    source.subscribe(val => console.log(val))*/

    /*        const source = Rx.Observable.from(["hel", "oo"]).map(x => x + "yoooo");
    source.subscribe(val => console.log(val))*/

    /*  const source = Rx.Observable(
    observer => {
    console.log("Observable create");
    observer.next("hello reactiveX");
    observer.next("another val");
    }
    )

    source.subscribe(x => console.log(x), err => console.log(err), complete => console.log(complete))
    */

    /*      const mypromise = new Promise((resolve, reject) => {
    console.log('create');
    setTimeout(() => {
    resolve("hi hghjgjg")
    }, 3000)
    })
    mypromise.then(x => {
    console.log("HHUUU");
    })
    const source = Rx.Observable.fromPromise(mypromise);
    source.subscribe(x => console.log(x))*/
    /*        const users = [{
    name: "hello",
    age: 11
    }, {
    name: "ggggo",
    age: 121
    }]
    const source = Rx.Observable.from(users).pluck('name');
    source.subscribe(x => console.log(x))

    Rx.Observable.interval(1000).take(3)
    .merge(Rx.Observable.interval(500).take(3))
    .subscribe(x => console.log(x))*/

    const clock = document.getElementById("clock");
    const clk = Rx.Observable.interval(1000).map(v => v + new Date().getTime());
    clk.subscribe(x => clock.innerHTML = x)

AMD loader实现

AMD相关文档: https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)

(function(global) {
    global = global || window;
    /**模块对象用来存放所有模块 */
    modules = {};
    /**正在加载的模块 */
    loadings = [];
    /**已经加载完成的模块 */
    loadedJs = [];

    /**
     * 获取模块方法
     * @param deps{Array} 依赖模块数组
     * @param callback{Function} 回调方法
     * @param parent{Object} 父对象
     */
    global.require = function(deps, callback, parent) {
        var id = parent || "amdloader" + Date.now();
        var cn = 0,
            dn = deps.length;
        var args = [];

        // dep为非绝对路径形式,而modules的key仍然需要绝对路径
        deps = deps.map(function(dep) {
            if (modules[dep]) { //jquery 
                return dep;
            } else if (dep in global.require.parsedConfig.paths) {
                return dep;
            }
            var rel = "";
            if (/^amdloader/.test(id)) {
                rel = global.require.parsedConfig.baseUrl;
            } else {
                var parts = parent.split('/');
                parts.pop();
                rel = parts.join('/');
            }
            return getModuleUrl(dep, rel);
        });

        /**定义模块并将模块加入到modules对象表 */
        var module = {
            id: id,
            deps: deps,
            factory: callback,
            state: 1,
            result: null
        };
        modules[id] = module;

        deps.forEach(function(dep) {
            if (modules[dep] && modules[dep].state === 2) {
                cn++
                args.push(modules[dep].result);
            } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
                loadJS(dep);
                loadedJs.push(dep);
            }
        });
        if (cn === dn) {
            callFactory(module);
        } else {
            loadings.push(id);
            checkDeps();
        }
    };

    /**
     * 模块加载的配置
     * @param config{Object} 配置对象
     */
    global.require.config = function(config) {
        this.parsedConfig = {};
        /**baseUrl :所有模块的查找根路径,基于baseUrl 来获取当前的url,并拼装完整的url地址 */
        if (config.baseUrl) {
            var currentUrl = getCurrentScript();
            var parts = currentUrl.split('/');
            parts.pop();
            var currentDir = parts.join('/');
            this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
        }
        var burl = this.parsedConfig.baseUrl;
        /**得到baseUrl后,location相对baseUrl进行拼装 */
        this.parsedConfig.packages = [];
        if (config.packages) {
            for (var i = 0, len = config.packages.length; i < len; i++) {
                var pck = config.packages[i];
                var cp = {
                    name: pck.name,
                    location: getRoute(burl, pck.location)
                }
                this.parsedConfig.packages.push(cp);
            }
        }


        this.parsedConfig.paths = {};
        /**paths :paths映射那些不直接放置于baseUrl下的模块名.也可以拼装baseUrl下的模块 */
        if (config.paths) {
            for (var p in config.paths) {
                this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
            }
        }

        /** shim中的内容没有通过define()来声明依赖关系、设置模块.通过define定义模块并由exports或item.exports 将其输出 */
        if (config.shim) {
            for (var p in config.shim) {
                var item = config.shim[p];
                define(p, item.deps, function() {
                    var exports;
                    if (item.init) {
                        exports = item.init.apply(item, arguments);
                    }

                    return exports ? exports : item.exports;
                });
            }
        }

        console.log(this.parsedConfig);
    }

    /**
     * 模块定义
     * @param id{String} 模块id
     * @param deps{Array} 依赖模块数组
     * @param callback{Function} 回调方法
     */
    global.define = function(id, deps, callback) {
        //加上moduleId的支持
        if (typeof id !== "string" && arguments.length === 2) {
            callback = deps;
            deps = id;
            id = "";
        }
        var id = id || getCurrentScript();
        if (modules[id]) {
            console.error('multiple define module: ' + id);
        }
        //请求依赖模块,当前id作为父模块
        require(deps, callback, id);
    };

    global.define.amd = {}; //AMD规范

    /**
     * 获取路径
     * @param base{String} baseurl路径
     * @param target{String} 依赖模块数组
     * @return url{String} 拼接的最终url
     */
    function getRoute(base, target) {
        var bts = base.replace(/\/$/, "").split('/'); //base dir
        var tts = target.split('/'); //target parts
        while (isDefined(tts[0])) {
            if (tts[0] === '.') {
                return bts.join('/') + '/' + tts.slice(1).join('/');
            } else if (tts[0] === '..') {
                bts.pop();
                tts.shift();
            } else if (tts[0] === '') {
                return bts.join('/');
            } else {
                return bts.join('/') + '/' + tts.join('/');
            }
        }
    };

    function isDefined(v) {
        return v !== null && v !== undefined;
    }

    /**
     * 获取模块路径,基于relative
     * @param moduleId{String} 模块id
     * @param relative{String} 基本url
     * @return url{String} 拼接的最终url
     */
    function getModuleUrl(moduleId, relative) {
        function getPackage(nm) {
            for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
                var pck = require.parsedConfig.packages[i];
                if (nm === pck.name) {
                    return pck;
                }
            }
            return false;
        }
        var mts = moduleId.split('/');
        var pck = getPackage(mts[0]);
        //若package存在先通过package路径拼装url
        if (pck) {
            mts.shift();
            return getRoute(pck.location, mts.join('/'));
        } else if (mts[0] === '.' || mts[0] === '..') {
            return getRoute(relative, moduleId);
        } else {
            return getRoute(require.parsedConfig.baseUrl, moduleId);
        }
    }

    /**加载js文件
     * @param url{String} js路径
     */
    function loadJS(url) {
        var script = document.createElement('script');
        script.type = "text/javascript";
        //判断模块是否在paths中定义了路径
        script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';
        script.onload = function() {
            var module = modules[url];
            if (module && isReady(module) && loadings.indexOf(url) > -1) {
                callFactory(module);
            }
            checkDeps();
        };
        var head = document.getElementsByTagName('head')[0];
        head.appendChild(script);
    };

    function checkDeps() {
        for (var p in modules) {
            var module = modules[p];
            if (isReady(module) && loadings.indexOf(module.id) > -1) {
                callFactory(module);
                checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
            }
        }
    };

    function isReady(m) {
        var deps = m.deps;
        var allReady = deps.every(function(dep) {
            return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
        })
        if (deps.length === 0 || allReady) {
            return true;
        }
    };

    function callFactory(m) {
        var args = [];
        for (var i = 0, len = m.deps.length; i < len; i++) {
            args.push(modules[m.deps[i]].result);
        }
        m.result = m.factory.apply(window, args);
        m.state = 2;

        var idx = loadings.indexOf(m.id);
        if (idx > -1) {
            loadings.splice(idx, 1);
        }
    };

    function getCurrentScript(base) {
        // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
        var stack;
        try {
            a.b.c(); //强制报错,以便捕获e.stack
        } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
            stack = e.stack;
            if (!stack && window.opera) {
                //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
                stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
            }
        }
        if (stack) {
            /**e.stack最后一行在所有支持的浏览器大致如下:
             *chrome23:
             * at http://113.93.50.63/data.js:4:1
             *firefox17:
             *@http://113.93.50.63/query.js:4
             *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
             *@http://113.93.50.63/data.js:4
             *IE10:
             *  at Global code (http://113.93.50.63/data.js:4:1)
             *  //firefox4+ 可以用document.currentScript
             */
            stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
            stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符
            return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
        }
        var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
        for (var i = nodes.length, node; node = nodes[--i];) {
            if ((base || node.className === moduleClass) && node.readyState === "interactive") {
                return node.className = node.src;
            }
        }
    };
})(window)

调用:

	window.something = "Bodhi";
	require.config({
		baseUrl: "./",
		packages: [{
			name: "more",
			location: "./more"
		}, {
			name: "mass",
			location: "../"
		}, {
			name: "wab",
			location: "../../../"
		}],
		shim: {
			"something": {
				"deps": ['jquery'],
				exports: 'something',
				init: function(jq, ol) {
					console.log(jq);
					return something + " in shim";
				}
			}
		},
		paths: {
			'jquery': "jquery"
		}
	});
	require([
		'bbb',
		'aaa.bbb.ccc',
		'ccc', 
		'something'
	], function(aaabbbccc) {
		console.log('simple loader');
		console.log(arguments);
	});

vue基础要点

版本2.0,参考:http://www.jianshu.com/p/5ba253651c3b
VueRouter 路由功能
app.vue

<template>
  <div id="app">
    <router-link to="/first">Go to Foo</router-link>
    <router-link to="/second">Go to Bar</router-link> 
    <router-view></router-view>
  </div>
</template>

main.js

import Vue from 'vue'
import App from './App'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
Vue.config.debug = true

Vue.use(VueRouter)
Vue.use(VueResource)
const First = { template: '<div><h2>我是第 1 个子页面</h2></div>' }
import secondcomponent from './components/Secondcomponent.vue'

// 创建一个路由器实例
// 并且配置路由规则
const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    {
      path: '/first',
      component: First
    },
    {
      path: '/second',
      component: secondcomponent
    }
  ]
})
// 现在我们可以启动应用了!
// 路由器会创建一个 App 实例,并且挂载到选择符 #app 匹配的元素上。
const app = new Vue({
  router: router,
  render: h => h(App)
}).$mount('#app')

ES6核心特性

ES6最常用的特性

let, const, class, extends, super, arrow functions, template string, destructuring, default, rest arguments
let,const 用于声明变量
let命令所在的代码块内有效

  if(1){ let a = 0; } 
  console.log(a);//显示Uncaught ReferenceError: a is not defined

let可以避免用来计数的循环变量泄露为全局变量

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

const也用来声明常量。一旦声明,常量的值就不能改变。

const WIDTH = 100; 
WIDTH = 2;//Uncaught TypeError: Assignment to constant variable.

class, extends, super

class Animal {
    constructor(){
        this.type = 'animal'
    }
    says(say){
        console.log(this.type + ' says ' + say)
    }
}
let animal = new Animal()
animal.says('hello') //animal says hello
class Cat extends Animal {
    constructor(){
        super()
        this.type = 'cat'
    }
}
let cat = new Cat()
cat.says('hello') //cat says hello

constructor就是构造方法,而this关键字则代表实例对象。constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。

Class之间可以通过extends关键字实现继承,上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。

super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

ES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

arrow function

简化了function写法

function(i){ return i + 1; } //常用写法
(i) => i + 1 //ES6
function(x, y) { 
    x++;
    y--;
    return x + y;
}//常用写法
(x, y) => {x++; y--; return x+y} //ES6

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

        setTimeout( () => {
            console.log(this.type + ' says ' + say)
        }, 1000)

template string

用反引号(`)来标识起始,用${}来引用变量,而且所有的空格和缩进都会被保留在输出之中

var user = {name: "boo"}; 
alert(`hello ${user.name}`);

destructuring

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

var [a,b,c] = [1,2,3];
var {foo,bar} = {foo:'aaa',bar:'bbb'}

default, rest

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

//传统写法
function animal(type){
    type = type || 'cat'  
    console.log(type)
}
animal()
//es6
function animal(type = 'cat'){
    console.log(type)
}
animal()
//rest参数(不定参数)
function animals(...types){
    console.log(types)
}
animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]

mongodb的安装启动

mac下安装启动MongoDB很简单,terminal下运行以下指令就可以了

  1. 通过homebrew安装mongodb

    $ brew update

    $ brew install mongodb

    如果没有homebrew还是先装一个吧,程序员必备。

  2. mongodb 数据默认存在/data/db下,所以需要创建这个文件夹

    $ sudo mkdir -p /data/db

    $ sudo chown xxx /data/db

    请把xxx替换为自己当前的用户名,如果不确定可以先run $ whoami

  3. 把mongodb/bin加入$PATH

    $ touch .bash_profile

    $ vim .bash_profile

    加入以下地址以后重启terminal

export MONGO_PATH=/usr/local/mongodb
export PATH=$PATH:$MONGO_PATH/bin
这里的path要根据具体版本的安装目录来写,具体看/usr/local/目录下的内容

  1. 启动mongodb
    $ mongod

  2. query database

    在另一个terminal窗口运行

    $ mongo

    然后可以开始各种数据库指令,比如

    $show dbs 显示已经存在的数据库

    $use somedbname 创建(使用)某个数据库

  3. 退出

    $exit

javascript算法描述

列表:

function List(){
  this.listSize = 0;
  this.pos = 0;
  this.dataStore = [];
  this.clear = clear;
  this.find = find;
  this.toString = toString;
  this.insert = insert;
  this.append = append;
  this.remove = remove; 
  this.front = front;
  this.end = end;
  this.prev = prev;
  this.next = next;
  this.length = next;
  this.currPos = currPos;
  this.moveTo = moveTo;
  this.getElement = getElement;
  this.contains =  contains;
}

function append(element){
  this.dataStore[this.listSize++] = element;
}

function find(element){
  for(var i = 0;i<this.dataStore.length;++i){
    if(this.dataStore[i] == element){
      return i;
    }
  }
  return -1;
}

function remove(element){
  var foundAt = this.find(element);
  if(fonndAt>-1){
    this.dataStore.splice(foundAt,1);
    --this.listSize;
    return true;
  }
  return false;
}

function length(){
  return this.listSize;
}

function toString(){
  return this.dataStore;
}

function insert(element, after){
  var insertPos = this.find(after);
  if(insertPos>-1){
    this.dataStore.splice(insertPos+1,0,element);
  }
}

function clear(){
  delete this.dataStore;
  this.dataStore = [];
  this.listSize = this.pos = 0;
}

function contains(){
  for(var i = 0;i<this.dataStore.length; ++i){
    if(this.dataStore[i]==element){
      return true;
    }
  }
  return false;
}

function front(){
  this.pos = 0;
}

function end(){
  this.pos = this.listSize-1;
}

function prev(){
  if(this.pos>0){
    --this.pos; 
  }
}

function next(){
  if(this.pos<this.listSize-1){
    ++this.pos; 
  }
}

function currPos(){
  return this.pos;
}

function moveTo(pos){
 this.pos = pos;
}

function getElement(){
  return this.dataStore[this.pos];
}

遍历方式
for(names.front();names.currPos()<names.length;names.next()){
console.log(names.getElement());
}

js求笛卡尔积

  cartesian() {
    return Array.prototype.reduce.call(arguments, function (a, b) {
      var ret = [];
      a.forEach(function (a) {
        b.forEach(function (b) {
          ret.push(a.concat([b]));
        });
      });
      return ret;
    }, [[]]);
  }

为什么前端监控要用 GIF 打点

没有跨域问题;
不会阻塞页面加载,影响用户体验;
在所有图片中体积最小,相较 BMP/PNG,可以节约 41%/35% 的网络资源。

二叉树遍历理解

    a

b -- c
d - e--f - g
先序:abc abdecfg
中序: bac dbeafcg
后序: bca debfgca

常用模式

工厂方法模式
// 通过对类的抽象,使得其可以用于创建多类实例。
// 安全模式创建工厂类

var Factory = function(type, content){
  if(this instanceof Factory){
    var s = new this[type](content);
    return s;
  }
  else{
    return new Factory(type, content)
  }
}

// 原型中设置创建类型数据的基类
Factory.prototype = {
  java: function(content){},
  javascript: function(content){}
}

var data = [{type: 'java', content: 'aaaaaaa'},
            {type: 'java', content: 'bbbbb'},
            {type: 'javascript', content: 'ccc'}];

for(var i = 2; i>=0 ; i--){
  Factory(data[i].type, data[i].content)
}

观察者模式的实现

发布订阅者模式,消息机制,解决主体对象与观察者之间功能耦合。

var observer = (function(){
  var _msg = {};
  return {
    regist: function(type, fn){
      console.log(_msg[type])
      if(typeof _msg[type] === 'undefined'){
        _msg[type] = [fn]
      }else{
        _msg[type].push(fn)
      }
    },
    fire: function(type, args){
      if(!_msg[type]) return;
      var events = {
        type:type,
        args: args || {}
      },
      i = 0, 
      len = _msg[type].length;
      for(; i< len; i++){
        _msg[type][i].call(this, events)
      }
    },
    remove: function(type, fn){
      if(_msg[type] instanceof Array){
        var i = _msg[type].length -1;
        for(; i>0; i--){
          _msg[type][i] === fn && _msg[type].splice(i,1);
        }
      }
    }
  }
})();
observer.regist('test', (e)=>{console.log(e.type, e.args.msg)})
observer.regist('test', (e)=>{console.log(e.type, e.args.msg)})
observer.remove('test', (e)=>{console.log(e.type, e.args.msg)})
observer.fire('test', {msg: 'sssss'})

用node实现json和jsonp服务

node代码

var http = require('http');
var urllib = require('url');

var port = 10011;
var data = {'name': 'jifeng', 'company': 'taobao'};

http.createServer(function(req, res){
  var params = urllib.parse(req.url, true);
  console.log(params);
  if (params.query && params.query.callback) {
    //console.log(params.query.callback);
    var str =  params.query.callback + '(' + JSON.stringify(data) + ')';//jsonp
    res.end(str);
  } else {
    res.end(JSON.stringify(data));//普通的json
  }     
}).listen(port, function(){
  console.log('server is listening on port ' + port);  
})

调用

function get_jsonp() {  
  $.getJSON("http://10.232.36.110:10011?callback=?",  
  function(data) {
    $('#result').val('My name is: ' + data.name);  
  });  
}  

为什么使用柯里化函数

这就是函数式的**, 用已有的函数组合出新的函数, 而柯里化每消费一个参数, 都会返回一个新的部分配置的函数, 这为函数组合提供了更灵活的手段, 并且使得接口更为流畅.
常见作用: 1. 参数复用;2. 提前返回;3. 延迟计算/运行。

function curry(fn, args) {
    console.log(fn,args)
    var fnlen = fn.length;

    var args = args || [];

    return function() {

        var new_args = args.slice(0);
 
        allargs = new_args.concat([].slice.call(arguments))
         
        if (allargs.length < fnlen) {
            return curry.call(this, fn, allargs);
        }
        else {
            return fn.apply(this, allargs);
        }
    }
}


var fn = curry(function(a, b, c, d) {
    console.log([a, b, c, d]);
});

fn("a", "b", "c","d") 
//fn("a", "b")("c")('f') 
//fn("a")("b")("c")("c") 
//fn("a")("b", "c")("c") 

尾递归和尾调用

当一个函数执行时的最后一个步骤是返回另一个函数的调用,这就叫做尾调用。
造成这样的结果是因为每个函数在调用另一个函数的时候,没有return该调用,所以执行引擎会认为你还没有调用完毕,会保留调用帧。
而如果使用尾调用优化,返回函数,调用帧就永远只有一条,这个时候就会节省很大一部分的内存空间,维护了代码运行的流畅性。

尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function tailFactorial (num, total) {
    if (num === 1) return total;
    return tailFactorial(num - 1, num * total);
}

function factorial (num) {
    return tailFactorial(num, 1);
}

factorial(5);                // 120
factorial(10);               // 3628800

双向数据绑定 defineproperty和proxy

1.基于数据劫持实现的双向绑定的特点

1.1 什么是数据劫持

数据劫持比较好理解,通常我们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,从而进行进一步操作。

// 这是将要被劫持的对象
const data = {
  name: '',
};

function say(name) {
  if (name === '古天乐') {
    console.log('给大家推荐一款超好玩的游戏');
  } else if (name === '渣渣辉') {
    console.log('戏我演过很多,可游戏我只玩贪玩懒月');
  } else {
    console.log('来做我的兄弟');
  }
}

// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      console.log('get');
    },
    set: function(newVal) {
      // 当属性值发生变化时我们可以进行额外操作
      console.log(`大家好,我系${newVal}`);
      say(newVal);
    },
  });
});

data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月

1.2 数据劫持的优势所在:

无需显示调用: 例如Vue运用数据劫持+发布订阅,直接可以通知变化并驱动视图,上面的例子也是比较简单的实现data.name = '渣渣辉'后直接触发变更,而比如Angular的脏检测则需要显示调用markForCheck(可以用zone.js避免显示调用,不展开),react需要显示调用setState。

可精确得知变化数据:还是上面的小例子,我们劫持了属性的setter,当属性值改变,我们可以精确获知变化的内容newVal,因此在这部分不需要额外的diff操作,否则我们只知道数据发生了变化而不知道具体哪些数据变化了,这个时候需要大量diff来找出变化值,这是额外性能损耗。

1.3 基于数据劫持双向绑定的实现思路

数据劫持是双向绑定各种方案中比较流行的一种,最著名的实现就是Vue。

基于数据劫持的双向绑定离不开Proxy与Object.defineProperty等方法对对象/对象属性的"劫持",我们要实现一个完整的双向绑定需要以下几个要点。

利用Proxy或Object.defineProperty生成的Observer针对对象/对象的属性进行"劫持",在属性发生变化后通知订阅者
解析器Compile解析模板中的Directive(指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染
Watcher属于Observer和Compile桥梁,它将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化

2.基于Object.defineProperty双向绑定的特点
2.1 极简版的双向绑定
我们都知道,Object.defineProperty的作用就是劫持一个对象的属性,通常我们对属性的getter和setter方法进行劫持,在对象的属性发生变化时进行特定的操作。

我们就对对象obj的text属性进行劫持,在获取此属性的值时打印'get val',在更改属性值的时候对DOM进行操作,这就是一个极简的双向绑定。

const obj = {};
Object.defineProperty(obj, 'text', {
  get: function() {
    console.log('get val');&emsp;
  },
  set: function(newVal) {
    console.log('set val:' + newVal);
    document.getElementById('input').value = newVal;
    document.getElementById('span').innerHTML = newVal;
  }
});

const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
  obj.text = e.target.value;
})

2.2 使用watcher来根据数据变化来更新视图
2.3 Object.defineProperty的缺陷
是的,Object.defineProperty的第一个缺陷,无法监听数组变化。 然而Vue的文档提到了Vue是可以检测到数组变化的,但是只有以下八种方法,vm.items[indexOfItem] = newValue这种是无法检测的。

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

3.Proxy实现的双向绑定的特点
3.1 Proxy可以直接监听对象而非属性
我们还是以上文中用Object.defineProperty实现的极简版双向绑定为例,用Proxy进行改写。

const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    if (key === 'text') {
      input.value = value;
      p.innerHTML = value;
    }
    return Reflect.set(target, key, value, receiver);
  },
});

input.addEventListener('keyup', function(e) {
  newObj.text = e.target.value;
});

3.2 Proxy可以直接监听数组的变化
当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length的变化,我们可以借此进行操作,以上文中Object.defineProperty无法生效的列表渲染为例。

webpack Uglifyjs unexpeected token: name($)

在全局使用jq,使用extenal,不需要专门作为模块。在使用webpack打包后,出现报错。主入口js中使用了let $=require('jQuery');会无法编译,let改为var即可

同步模块模式

var F = F ||{}
F.define = function(str, fn){
  var parts = str.split('.'),
      old = parent = this,
      i = len =0;
  
  if(parts[0]==='F'){
    parts = parts.slice(1);
  }
  
  if(parts[0]==='define' || parts[0]==='module'){
    return;
  }
  
  for(len = parts.length; i<len; i++){
    if(typeof parent[parts[i]] === 'undefined'){
      parent[parts[i]] = {}
    }
    old = parent;
    parent = parent[parts[i]]
  }
  
  if(fn){
    old[parts[--i]] = fn();
  }
  
  return this;
}

F.mudule = function(){
  var args = [].slice.call(arguments),
      fn = args.pop(),
      parts = args[0] && args[0] instanceof Array ? args[0] : args,
      modules = [],
      modIDs = '',
      i = 0,
      ilen = parts.length,
      parent,
      j,
      jlen;
  
  while(i<ilen){
    if(typeof parts[i] === 'string'){
      parent = this;
      modIDs = parts[i].replace(/^F\./, '').split('.');
      for(j=0; jlen = modIDs.length; j<jlen; j++){
        parent = parent[modIDs[j]] || false;
      }
      modules.push(parent)
    }else{
      modules.push(parts[i])
    }
    i++;
  }
  
  fn.apply(null, modules)
  
}

常用正则

验证中文: /^[u4E00-u9FA5]+$/.test(v)
验证数字字母: !/^[A-Za-z0-9]+$/.test(v)
验证数字: !/^[1-9]\d*$/.test(v)
匹配指定字符开始和指定字符结束: /^a.*?b$/.test(v)
验证url:

function IsURL(str_url){
    var strRegex = /(http[s]?|ftp):\/\/[^\/\.]+?\..+\w$/i;
    var re=new RegExp(strRegex);
    if (re.test(str_url)){
        return (true);
    }else{
        return (false);
    }
}

验证email:

function CheckMail(mail) {
	 var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	 if (filter.test(mail)) return true;
	 else {  return false;}
}

只能输入数字,小数点后保留两位
!/^\d*.{0,2}\d{0,2}$/.test(v)

替换所有数字为指定字符
'aaa111aaa'.replace(/\d+/g, '3')

每个数字都替换
'aaa111aaa'.replace(/[0-9]/ig, '3')

为什么使用反柯里化

函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了固定参数, 延迟计算等。

那么反柯里化函数,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使得本来只有特定对象才适用的方法,扩展到更多的对象。

使用fiddler测试jsonp接口

打开Fiddler.
在AutoResponder里,添加Rule,
使用正则表达式匹配JSONP请求的URL
示例:
Rule Editor
regex:^http://jex.im/api.cgi\?callback=(.+)
http://localhost/test.php?callback=$1

比如针对微博请求接口,可以这样配置:
Rule Editor
regex:^http://jex.im/api/MicroBlogServlet\?(.+)
http://localhost/test.php?$1

这样请求http://jex.im/api/MicroBlogServlet?jsoncallback=MTHEQOQT5C6G0PM2&type=netease&count=10&uid=1
就会取请求到http://localhost/test.php?jsoncallback=MTHEQOQT5C6G0PM2&type=netease&count=10&uid=1
的数据

javascript算法描述2

集合
set类实现

function Set(){
  this.dataStore = [];
  this.add = add;
  this.remove = remove;
  this.size = size;
  this.union = union;
  this.intersect = intersect;
  this.subset = subset;
  this.difference = defference;
  this.show = show;
}

function add(data){
  if(this.dataStore.indexOf(data)<0){
    this.dataStore.push(data);
    return true;
  }else{
    return false;
  }
}

function remove(data){
  var pos = this.dataStore.indexOf(data);
  if(pos>-1){
    this.dataStore.slice(pos,1);
    return true;
  }else{
    return false;
  }
}

function show(){
  return this.dataStore;
}

function contains(data){
  if(this.dataStore.indexOf(data)>-1){
    return true;
  }else{
    return false;
  }
}

function union(set){
  var tempSet = new Set();
  for(var i=0; i<this.dataStore.length; ++i){
    tempSet.add(this.dataStore[i]);
  }
  for(var i=0; i<set.dataStore.length; ++i){
    if(!tempSet.contains(set.dataStore[i]))
    tempSet.dataStore.push(set.dataStore[i]);
  }
  return tempSet;
}

function intersect(set){
  var tempSet = new Set();
  for(var i=0; i<this.dataStore.length; ++i){
    if(set.contains(this.dataStore[i]))
    tempSet.add(this.dataStore[i]);
  }
  return tempSet;
}

function subset(set){
  if(this.size()>set.size()){
    return false;
  }else{
    for each(var member in this.dataStore){
      if(!set.contains(member)){
        return false;
      }
    }
  }
  return true;
}

function size(){
  return this.dataStore.length;
}

function difference(set){
  var tempSet = new Set();
  for(var i=0; i<this.dataStore.length; ++i){
    if(!set.contains(this.dataStore[i])){
      tempSet.add(this.dataStore[i])
    }
  }
  return tempSet;
}

javascript重点特性(闭包,变量对象VO)

闭包

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

无非就是变量解析的过程。

首先看一段话:
每次定义一个函数,都会产生一个作用域链(scope chain)。当JavaScript寻找变量varible时(这个过程称为变量解析),总会优先在当前作用域链的第一个对象中查找属性varible ,如果找到,则直接使用这个属性;否则,继续查找下一个对象的是否存在这个属性;这个过程会持续直至找到这个属性或者最终未找到引发错误为止。

所以,JavaScript中的所谓的高大上的闭包其实很简单,根本上还是变量解析。而之所以可以实现,还是因为变量解析会在作用域链中依次寻找对应属性的导致的。

就是一种允许函数向关联的父级作用域寻址的访问特权。

Javascript作用链域

全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,
直至全局函数,这种组织形式就是作用域链。

webpack要点记录

1.Code Splitting

  • 管理加载顺序
  • 合并相同代码
  • 模块管理
    main.js
//Code Splitting维护了两个依赖树,一个是cmd标准的同步依赖树,另外一个是amd标准的异步依赖树。
//当使用同步依赖的适合,使用require(path),path是模块的路径
//当需要使用异步依赖的适合,使用require.ensure([deps], callback),deps是依赖的模块,callback是回调,当deps里面的模块加载完毕后,执行callback回调函数。
var $ = require('jquery');
$(function(){
	$('input[type=button]').click(function(){
		require.ensure(['./a'], function(require) {
  			var content = require('./a');
  			console.log('click...........');
  			console.log(content);
		});
	});
});

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js',
    path: 'build'
  }
};

a.js

module.exports = 'Hello World';

index.html

<html>
  <body>
   	<input type="button" value="click me">
  </body>
   <script src="./bundle.js"></script>
</html>

webpack会维持一堆代码块modules,代码块0,是所有代码的开端,然后通过id 寻找需要加载的代码块,如果遇到amd加载的代码块,需要把代码块回写到modules里。

MVVM实现

对一个现有demo进行修改和注释,方便阅读

<!DOCTYPE html>
<html>

<head>
</head>

<body>
    <!--多维数组-->
    <div id="root">
        <div v-model="user"></div>
        <ul v-list="todos">
            <h1 v-model="title"></h1>
            <div>
                <li v-list-item="todos">
                    <p v-class="todos:done" v-model="todos:creator"></p>
                    <p v-model="todos:content"></p>
                    <ul v-list="todos:members">
                        <li v-list-item="todos:members">
                            <span v-model="todos:members:name"></span>
                        </li>
                    </ul>
                </li>
            </div>
        </ul>
    </div>
    <!--双向数据绑定-->
    <div id="bind">
        <h1 v-model="title"></h1>
        <input v-model="content" v-event="input" type="text" />
        <p v-model="content"></p>
        <p v-model="content"></p>
    </div>
    <!--数组深度嵌套-->
    <div id="arr">
        <ul v-list="todos:words:arr">
            <li v-list-item="todos:words:arr">
                <div>
                    <span v-model="todos:words:arr"></span>
                </div>
            </li>
        </ul>
    </div>
</body>
<script>
    class mvvm {
        constructor(mvvm) {
            if (!mvvm.el || !mvvm.data) {
                throw new Error('mvvm need an object to observe.');
            }
            //数据
            this.$data = mvvm.data;
            //观察器
            this.$watcher = new Watcher();
            //查询dom对象
            this.$el = document.querySelector(mvvm.el);
            //事件列表
            this.$eventList = mvvm.eventList;
            //dom片段
            this.$fragment = this.nodeToFragment(this.$el);
            //解析dom片段
            this.parser(this.$fragment);
            //dom追加到页面
            this.$el.appendChild(this.$fragment);
            //开始观察
            this.$watcher.build();
        }

        /** 获取节点并将节点下子内容追加到一个DocumentFragment */
        nodeToFragment(el) {
            const fragment = document.createDocumentFragment();
            let child = el.firstChild;
            while (child) {
                fragment.appendChild(child);
                child = el.firstChild;
            }
            return fragment;
        }

        /** 扫描并解析html节点 */
        parser(node) {
            if (node === this.$fragment || !node.getAttribute('v-list')) {
                let childs = [];
                //将子节点转为数组
                if (node.children) {
                    childs = [...node.children];
                } else {
                    [...node.childNodes].forEach((child) => {
                        if (child.nodeType === 1) {
                            childs.push(child);
                        }
                    });
                }
                console.log(childs);
                childs.forEach((child) => {
                    //设置path
                    if (node.path) {
                        child.path = node.path;
                    }
                    this.parseEvent(child);
                    this.parseClass(child);
                    this.parseModel(child);
                    if (child.children.length) {
                        //解析子节点
                        this.parser(child);
                    }
                });
            } else {
                this.parseList(node);
            }
        }

        /** 解析数据 
         * param 属性值{string}
         * param 节点对象{HTMLElement}
         * return 对象{path:属性解析为数组['todos',':','creator'], data:数据对象对应的值}
         */
        parseData(str, node) {
            const list = str.split(':');
            let data;
            let nowPath;
            let arrayCounter = 1;
            const path = [];
            list.forEach((key, index) => {
                if (index === 0) {
                    data = this.$data[key];
                } else if (node.path) {
                    nowPath = node.path[arrayCounter];
                    arrayCounter += 1;
                    if (nowPath === key) {
                        data = data[key];
                    } else {
                        path.push(nowPath);
                        data = data[nowPath][key];
                        arrayCounter += 1;
                    }
                } else {
                    data = data[key];
                }
                path.push(key);
            });
            if (node.path && node.path.length > path.length) {
                const lastPath = node.path[node.path.length - 1];
                //if (typeof lastPath === 'number') {
                data = data[lastPath];
                path.push(lastPath);
                //}
            }
            //if (!node.path || node.path !== path) {
            node.path = path;
            //}
            return {
                path,
                data
            };
        }

        parseEvent(node) {
            if (node.getAttribute('v-event')) {
                const eventName = node.getAttribute('v-event');
                const type = this.$eventList[eventName].type;
                const fn = this.$eventList[eventName].fn.bind(node);
                if (type === 'input') {
                    let cmp = false;
                    node.addEventListener('compositionstart', () => {
                        cmp = true;
                    });
                    node.addEventListener('compositionend', () => {
                        cmp = false;
                        node.dispatchEvent(new Event('input'));
                    });
                    node.addEventListener('input', function input() {
                        if (!cmp) {
                            const start = this.selectionStart;
                            const end = this.selectionEnd;
                            fn();
                            this.setSelectionRange(start, end);
                        }
                    });
                } else {
                    node.addEventListener(type, fn);
                }
            }
        }

        /*
         * 解析自定class属性 并添加class
         * param html节点{HTMLElement}
         *
         */
        parseClass(node) {
            if (node.getAttribute('v-class')) {
                const className = node.getAttribute('v-class');
                const dataObj = this.parseData(className, node);
                if (!node.classList.contains(dataObj.data)) {
                    node.classList.add(dataObj.data);
                }
                this.$watcher.regist(this.$data, dataObj.path, (old, now) => {
                    node.classList.remove(old);
                    node.classList.add(now);
                });
            }
        }

        parseModel(node) {
            if (node.getAttribute('v-model')) {
                const modelName = node.getAttribute('v-model');
                const dataObj = this.parseData(modelName, node);
                if (node.tagName === 'INPUT') {
                    node.value = dataObj.data;
                } else {
                    node.innerText = dataObj.data;
                }
                this.$watcher.regist(this.$data, dataObj.path, (old, now) => {
                    if (node.tagName === 'INPUT') {
                        node.value = now;
                    } else {
                        node.innerText = now;
                    }
                });
            }
        }

        parseList(node) {
            console.log('NODE')
            console.log(node.path)
            const parsedItem = this.parseListItem(node);
            const itemEl = parsedItem.itemEl;
            const parentEl = parsedItem.parentEl;
            const list = node.getAttribute('v-list');
            const listData = this.parseData(list, node);
            listData.data.forEach((_dataItem, index) => {
                const copyItem = itemEl.cloneNode(true);
                copyItem.$data = _dataItem;
                //if (node.path) {
                copyItem.path = node.path.slice();
                //}
                copyItem.path.push(index);
                this.parseEvent(copyItem);
                this.parseClass(copyItem);
                this.parseModel(copyItem);
                this.parser(copyItem);
                parentEl.insertBefore(copyItem, itemEl);
            });
            parentEl.removeChild(itemEl);
            this.$watcher.regist(this.$data, listData.path, () => {
                while (parentEl.firstChild) {
                    parentEl.removeChild(parentEl.firstChild);
                }
                const thisListData = this.parseData(list, node);
                parentEl.appendChild(itemEl);
                thisListData.data.forEach((_dataItem, index) => {
                    const copyItem = itemEl.cloneNode(true);
                    copyItem.$data = _dataItem;
                    if (node.path) {
                        copyItem.path = node.path.slice();
                    }
                    copyItem.path.push(index);
                    this.parseEvent(copyItem);
                    this.parseClass(copyItem);
                    this.parseModel(copyItem);
                    this.parser(copyItem);
                    parentEl.insertBefore(copyItem, itemEl);
                });
                parentEl.removeChild(itemEl);
            });
        }

        parseListItem(node) {
            const me = this;
            let target;
            (function getItem(nodeToScan) {
                [...nodeToScan.children].forEach((thisNode) => {
                    if (nodeToScan.path) {
                        thisNode.path = nodeToScan.path.slice();
                    }
                    if (thisNode.getAttribute('v-list-item')) {
                        target = {
                            itemEl: thisNode,
                            parentEl: nodeToScan
                        }
                    } else {
                        me.parseEvent(thisNode);
                        me.parseClass(thisNode);
                        me.parseModel(thisNode);
                        getItem(thisNode);
                    }
                });
            }(node));
            return target;
        }
    }


    class Watcher {
        constructor() {
            this.routes = [];
        }

        regist(obj, k, fn) {
            const route = this.routes.find((el) => {
                let result;
                if ((el.key === k || el.key.toString() === k.toString()) && Object.is(el.obj, obj)) {
                    result = el;
                }
                return result;
            });
            if (route) {
                route.fn.push(fn);
            } else {
                this.routes.push({
                    obj,
                    key: k,
                    fn: [fn],
                });
            }
        }

        //对每个route的object 进行观察
        build() {
            this.routes.forEach((route) => {
                observer(route.obj, route.key, route.fn);
            });
        }
    }



    /** 观察数据变化 
     * param data 数据对象{Object}
     * param path 节点属性数组或对象{Array || Object}
     * param 回调函数{Function}
     */
    function observer(obj, k, callback) {
        if (!obj || (!k && k !== 0)) {
            throw new Error('Please pass an object to the observer.');
        }
        if (Object.prototype.toString.call(k) === '[object Array]') {
            observePath(obj, k, callback);
        } else {
            let old = obj[k];
            if (!old) {
                throw new Error('The key to observe is undefined.');
            }
            if (Object.prototype.toString.call(old) === '[object Array]') {
                observeArray(old, callback);
            } else if (old.toString() === '[object Object]') {
                observeAllKey(old, callback);
            } else {
                /** 当数据发生变化时会自动调用set方法, 获取数据时会自动调用get方法 */
                Object.defineProperty(obj, k, {
                    enumerable: true,
                    configurable: true,
                    get: () => old,
                    set: (now) => {
                        if (now !== old) {
                            callback.forEach((fn) => {
                                fn(old, now);
                            });
                        }
                        old = now;
                    },
                });
            }
        }
    }

    /*
     * 对path数组进行处理后再调用observer
     * param data 数据对象{Object}
     * param path 节点属性数组{Array}
     * param 回调函数{Function}
     */
    function observePath(obj, paths, callback) {
        let nowPath = obj;
        let key;
        paths.forEach((path, index) => {
            const path2Num = parseInt(path, 10);
            let pathArr = path;
            if (path2Num === pathArr) {
                pathArr = path2Num;
            }
            if (index < paths.length - 1) {
                nowPath = nowPath[pathArr];
            } else {
                key = pathArr;
            }
        });
        observer(nowPath, key, callback);
    }

    /*
     * 观察对数组对象操作的处理
     * param Array 数组对象{Array}
     * param 回调函数{Function}
     */
    function observeArray(arr, callback) {
        const oam = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
        const arrayProto = Array.prototype;
        const hackProto = Object.create(Array.prototype);
        oam.forEach((method) => {
            Object.defineProperty(hackProto, method, {
                writable: true,
                enumerable: true,
                configurable: true,
                value: function operateArray(...arg) {
                    const old = arr.slice();
                    const now = arrayProto[method].call(this, ...arg);
                    callback.forEach((fn) => {
                        fn(old, this, ...arg);
                    });
                    return now;
                },
            });
        });
        Object.setPrototypeOf(arr, hackProto);
    }

    function observeAllKey(obj, callback) {
        Object.keys(obj).forEach((key) => {
            observer(obj, key, callback);
        });
    }


    let data = {
        title: 'todo',
        user: 'mirone',
        todos: [{
            creator: 'keal',
            done: 'undone',
            content: 'writeMVVM',
            members: [{
                name: 'kaito'
            }]
        }]
    }
    new mvvm({
        el: '#root',
        data
    })

    data.todos[0].done = 'done'
    data.todos[0].members[0].name = 'eeeee'
    data.todos[0].members.push({
        name: 'hitoriqqqq'
    })
    data.title = 'todo list!'
    data.todos.push({
        creator: 'k',
        done: 'undone',
        content: 'get mvvm',
        members: [{
            name: 'hito'
        }]
    })
    data.todos[1].members.push({
        name: 'asuna'
    })

    //============
    let bind = {
        title: 'data bind',
        content: 'qaq'
    }
    let e = {
        'input': {
            type: 'input',
            fn: function() {
                bind.content = this.value
            }
        }
    }
    new mvvm({
        el: '#bind',
        data: bind,
        eventList: e
    })

    //===============
    let arr = {
        todos: {
            words: {
                arr: ['It\'s been a long day', 'with out you', 'my friend']
            }
        }
    }
    new mvvm({
        el: '#arr',
        data: arr,
    })
    arr.todos.words.arr.push('hello')
</script>

</html>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.