Hank's Blog

耕种思考的自留地

0%

Date 对象用于处理日期与实际,记录一下它的方法以及传参。

常用方法

方法 描述
getFullYear() 返回四位数字年份
getMonth() 返回月份 (0 ~ 11)
getDate() 返回一个月中的某一天 (1 ~ 31)
getHours() 返回 Date 对象的小时 (0 ~ 23)
getMinutes() 返回 Date 对象的分钟 (0 ~ 59)
getSeconds() 返回 Date 对象的秒数 (0 ~ 59)
getDay() 返回一周中的某一天 (0 ~ 6)
getTime() 返回 1970 年 1 月 1 日至今的毫秒数

new Date() 参数

  • new Date("month dd,yyyy hh:mm:ss") 这里的month是一个英文月份单词如January
  • new Date("yyyy/MM/dd hh:mm:ss")/分隔日期比用-等分隔兼容性更好
  • new Date(yyyy, MM, dd, hh, mm, ss) 参数不能放 Array 类型,且每项必须是 String 或 Number 类型
  • new Date(milliseconds) 参数必须是 Number 类型
1
2
3
4
5
6
7
8
9
10
11
new Date("January 12,2006 22:19:35"); // 注意格式 

new Date("January 12,2006"); // String 类型

new Date("2006/2/2 22:19:35"); // String 类型

new Date(2006,0,12,22,19,35); // 不能放 Array 类型

new Date(2006,0,12); // 可以是String 或 Number

new Date(1137075575000); // 必须是 Number 类型

Vue 的一个最明显的特性就是其不太引人注意的响应式系统。数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图会进行更新。
这是如何做到的呢?下面我将实现一个双向绑定的简单案例,我将分三步来实现:

  1. model -> view 初始化
  2. view -> model 绑定
  3. model -> view 绑定

学习过程需要结合代码(源码地址),
以上三步分别对应vue-es5下的step1.htmlstep2.htmlstep3.html

step1

第一步我们要考虑的应该是如何把

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
<input type="text" v-model="start">
<br> {{start}}
</div>
<script>
var vm = new Vue({
el: 'app',
data: {
start: 'hello world'
}
})
</script>

inputvalue值和{{start}}变为vm.data.start的值,即将 model 转化为 view 。我们需要一个方法,该方法可以实现以上的转化,让v-mode="start"{{start}}绑定到的data.start的值,代码如下,具体代码和效果见step1.html。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 劫持节点并转化为文档片段
function node2Fragment(node, vm) {
var flag = document.createDocumentFragment()
var child
while (child = node.firstChild) { // 编译每个节点,直到node下无子节点
compile(child, vm)
flag.appendChild(child) // appendchild方法会自动删除node的child节点(子节点有且仅有一个父节点)
}
return flag // 返回填充后的文档片段
}

// 编译节点
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/
if (node.nodeType === 1) { // 元素节点
var attr = node.attributes
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue.trim()
node.value = vm.data[name]
}
}
};
if (node.nodeType === 3) { // 文本节点
if (reg.test(node.nodeValue)) {
var name = RegExp.$1.trim()
node.nodeValue = vm.data[name]
}
}
}

// 创建Vue对象
function Vue(options) {
this.data = options.data
var node = document.getElementById(options.el)
var dom = node2Fragment(node, this)
// 将dom片段添加到目标元素
node.appendChild(dom)
}

step2

第二步需要实现view层向model层的绑定,当用户输入改变input的值(view层)时,反映到data中(model层)并改变对应的值。这里需要用到Object.defineProperty()来设置对象的访问器属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将vm.data上的数据挂载在vm上
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key])
})
}

// 创建响应式数据
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get: function() {
return val
},
set: function(newVal) {
if (newVal === val) return
val = newVal
console.log("新属性值为" + val)
}
})
}

把observe函数在vue构造器中调用

1
2
3
4
5
6
7
function Vue(options) {
this.data = options.data
var node = document.getElementById(options.el)
observe(this.data,this) // 监听数据
var dom = node2Fragment(node, this)
node.appendChild(dom)
}

同时我们也需要修改一下原来的compile函数,将vm.data[name]改为vm.[name]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/
if (node.nodeType === 1) {
var attr = node.attributes // 获取节点的所有属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue.trim()
node.addEventListener('input', function(e) {
vm[name] = e.target.value;
});
node.value = vm[name]
}
}
};
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1.trim()
node.nodeValue = vm[name]
}
}
}

这样当view层被改动时,相应的model层中对应的数据也会改变,具体代码和效果见step2.html。

step3

现在我们离双向绑定只差最后一步了,也是最重要和最难理解的一步,如何实现当model层中数据改变的时候响应式地改变view层的显示,即当改变input输入的时候能马上在下方视图得到显示。第一步做的是初始化绑定,现在要完成的是,当用户改变data值,再回过头去改变view层,这里将用到一个设计模式:观察者模式。
观察者模式是程序设计中的一种设计模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,让他们能够自动更新自己。下面代码是一个应用观察者模式的简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 创建一个主题类
function Dep () {
this.subs = [] // 主题的订阅者们
}
// 添加订阅者
Dep.prototype.addSub = function(sub) {
this.subs.push(sub)
};
// 发布公告
Dep.prototype.notify = function() {
this.subs.forEach(function(sub) {
sub.update()
})
};

// 创建一个订阅者类
function Watcher(name) {
this.name = name
}
// 更新自己
Watcher.prototype.update = function() {
console.log(this.name+'更新了')
};

// 实例化一个主题
var dep = new Dep()

// 实例化订阅者并添加到主题
var sub1 = new Watcher('sub1')
dep.addSub(sub1)
var sub2 = new Watcher('sub2')
dep.addSub(sub2)

// 主题发布公告,订阅者更新自己
dep.notify()

接下来我们要将该模式应用在我们的案例中,添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 创建一个主题类
function Dep() {
this.subs = [] // 主题的订阅者
}

Dep.prototype = {
// 添加订阅者
addSub: function(sub) {
this.subs.push(sub)
},
// 发布更新公告
notify: function() {
this.subs.forEach(function(sub) {
sub.update() // 触发对应属性的 setter
})
}
}

// 创建一个订阅者类
function Watcher(vm, node, name) {
Dep.target = this // 未订阅标记
this.name = name
this.node = node
this.vm = vm
this.update() // 初始化视图,触发对应属性的 getter
Dep.target = null // 已订阅标记
}

Watcher.prototype = {
update: function() {
this.node.nodeValue = this.vm[this.name] // 触发对应属性的 getter/setter
}
}

还需要修改一下defineReactive函数和compile函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function defineReactive(obj, key, val) {
var dep = new Dep() // 实例化一个主题
Object.defineProperty(obj, key, {
get: function() {
// 添加订阅者到主题
if (Dep.target) dep.addSub(Dep.target)
return val
},
set: function(newVal) {
if (newVal === val) return
val = newVal
dep.notify() // 发布更新公告
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function compile(node, vm) {
var reg = /\{\{(.*)\}\}/
if (node.nodeType === 1) {
var attr = node.attributes
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue.trim()
node.addEventListener('input', function(e) {
vm[name] = e.target.value
});
node.value = vm[name]
}
}
};
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1.trim()
new Watcher(vm, node, name) // 初始化数据并添加订阅者
}
}
}

至此我们已经实现了基础的双向绑定功能,具体代码和效果见step3.html。

总结

最后在理解一下 Vue 官网对其响应式原理的解释:Vue响应式原理

平时的工作中,经常会遇到渲染列表的需求,比如下面的代码

1
2
3
4
5
6
7
8
9
var data = [{id: 1, name: 'hanger'}, {id: 2, name: 'Alice'}]
var tableContent = ''
data.forEach(function (obj) {
tableContent += '<tr>' +
'<th >' + obj.id + '</th>' +
'<th>' + obj.name + '</th>' +
'</tr>'
})
console.log(tableContent)

向上面这样写一个字符串十分繁琐,而且不可复用。那么有什么办法可以解决这样的问题呢?
我们都知道js的replace()函数可以实现字符串的替换,replace()第二个参数可以为函数,在开启列表模板引擎进化之旅之前需要先理解replace方法
现在我要利用replace()的强大能力来解决上面这个问题。直接上最初的代码:

Read more »

let,const

let用于变量的声明,可以防止变量提升,实际上为JavaScript新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。const也用来声明变量,但是声明的是常量。一旦声明,常量的值就不能改变。

class,extends,super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal { // class定义一个“类”
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 { // extends实现继承
constructor(){
super() // 指代父类的this对象
this.type = 'cat'
}
}

let cat = new Cat()
cat.says('hello') //cat says hello
Read more »

定义字符串

PHP中定义字符串有三种方式(其中单引号和双引号的区别请详见这里):

  • 单引号 $hello = 'hello world';
  • 双引号 $hello = "hello world";
  • heredoc语法结构
    1
    2
    3
    $hello = <<<TAG
    hello world
    TAG;

操作字符串

方法 功能
. 连接字符串
trim()/rtrim()/ltrim() 去除(两边/右边/左边)空格
strlen()/mb_strlen() 获取字符串/中文字符串长度
substr($str,start,howmany) 截取字符串
md_substr($str,start,howmany,’utf8’) 截取中文字符串
strpos(要处理的字符串, 要定位的字符串, [定位的起始位置) 返回要定位的字符串索引
str_replace(被替换的字符串, 替换的字符串, 被搜索的字符串, [替换进行计数]) 替换字符串(区分大小写)
sprintf(格式, 要转化的字符串) 格式化字符串
implode(分隔符[可选], 数组) 把数组元素合并为一个字符串
explode(分隔符[可选], 字符串) 把一个字符串分隔为数组元素
addslashes() 转义字符串
Read more »

pug 是 HTML 的模板引擎,原名 jade ,大大简化了 HTML 代码的书写,并且增加了许多功能,代码之间的嵌套关系是统一通过** 空格 或者 tab 来实现的, 不能两者混合用 ,并且 1个tab不等于4个空格 **,请务必先记住这一点。

基础

pug 长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
doctype html  
head
meta(charset='utf-8')
title my resume
style.
body{background-color:#ABFDBA}
script.
var name = 'scoot'
body
- var jade = {source:'jade',level:'high'}
h2 a frondend enginner #{jade.level}
p
| Alice
| Tom
| Hairen
a(href='www.baidu.com') baidu
.skills haha
div.program lalla
ul#fruit.fruit
li aaa
li bbb
div: a hahhah

以上代码等价与:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html  >
<head>
<meta charset="utf-8"/>
<title>my resume </title>
<style>body{background-color:#ABFDBA}</style>
<script>var name = 'scoot' </script>
</head>
<body>
<h2>a frondend enginner high</h2>
<p>
Alice
Tom
Hairen <a href="www.baidu.com">baidu </a>
</p>
<div class="skills"> haha</div>
<div class="program"> lalla</div>
<ul class="friuts" id="fruit">
<li>aaa</li>
<li>bbb</li>
</ul>
<div><a>hahhah </a></div>
</body>
Read more »

当我们定义一个字符串、数组、对象等等的时候,我们习惯用字面量来定义,例如:

1
2
3
var s = "string";
var a = [1,2];
var o = {};

当需要加入变量时也十分简单:

1
2
3
4
var v = "bl";
var s = "string" + v; //"stringbl"
var a = [1,v]; //[1,"bl"]
var o = {first : v}; //{first : "bl"}

但是如果是正则的字面量,就不能像上面这样加入变量了。
此时应该采用构造函数的形式来加入变量:

1
2
var v = "bl";
var re = new RegExp("^\\d+" + v + "$","gim"); // re为/^\d+bl$/gim

记录下一些常用的 Mac 命令行。

关于man命令

在命令行中输入 man command-name 会返回一个该条命令的使用指南,非常详细。
使用指南往往很长,可以使用 ▲(上箭头)或 ▼(下箭头)来上下移动,使用空格键来翻页,输入/和关键字来搜索,按 Q 退出。

目录操作

命令 描述 用法
mkdir 创建一个目录 mkdir dir1
rmdir 删除一个空目录 rmdir dir1
mv 重命名一个目录/文件 mvdir dir1 dir2
rm -r 删除一个目录 rm -r dir1
cd 切换到指定目录 cd dirPath
ls 显示目录内容 ls (dir1)
pwd 显示目录路径 pwd

文件操作

命令 描述 用法

| 创建/覆盖文件(回车进入文件编辑,编辑的内容(最后一行之前的内容)会覆盖原文件内容,ctrl+c退出并保存编辑) | > file1

| 创建/追加文件(回车进入文件编辑,编辑的内容(最后一行之前的内容)会追加到原文件下一行,ctrl+c退出并保存编辑) | >> file1
cat | 显示文件内容 | cat flie1
more | 分屏显示文件内容 | more file1
cp | 复制文件或目录 | cp file1 file2
rm | 删除一个文件 | rm file1
flie | 显示文件类型 | file fiel1
open | 用默认程序打开文件 | open fiel1
nano | 默认编辑器打开文件 | nano fiel1
vim | 使用Vim编辑文件 | vim file1

Read more »

跨域是由浏览器的同源策略引起的,即不同源(协议,域名,端口中其中有一个不同)的js是不能读取对方的资源的。限制跨域是浏览器的行为,而不是JS的行为。要实现跨域请求,解决方法大致分为两类:

  • 一类是Hack,比如通过 title , navigation 等对象传递信息,JSONP可以说是一个最优秀的Hack。
  • 另一类是HTML5支持,一个是 Access-Control-Allow-Origin 响应头,一个是 window.postMessage 。

document.domain

原理:相同主域名不同子域名下的页面,可以设置 document.domain 让它们同域。
限制:同域document提供的是页面间的互操作,需要载入iframe页面。

Read more »

jQuery 用久了,原生JS操作 DOM 的方法就容易忘,在此好好归纳一下原生的常用方法。

创建元素

创建元素:document.createElement()

1
2
3
4
var div = document.createElement("div");
div.id = "myDiv";
div.className = "div1";
document.body.appendChild(div);

创建文本节点 :document.createTextNode()

1
2
var node = document.createTextNode("我是文本节点");  
document.body.appendChild(node);

节点关系

1
2
3
4
5
<div id="div0">
<div id="div1" name="nameone">1</div>
<div id="div2" class="div2">2</div>
<div id="div3">3</div>
</div>

父节点:parentNode

1
2
var child2 = document.getElementById("div2");
var parent = child2.parentNode;
Read more »