Francis Feng 10 X 10
Last updated: 2018-04-15
Francis Feng:~ Desktop$ node home.js

> Blog.purpose
Do or do not, there is no try.

> Blog.author
Francis Feng

> Blog.more
(937) 227-3760
简单实现一个Vue

前言

最近的团队分享上,我把之前阅读过的vue的早期的源码简单地分析了一下,自己动手写了一个简单版的FakeVue,简单讲解了一下vue的双向绑定机制是如何实现的,因为是早期的几个commit所以和最新的vue的代码肯定有不少的区别,不过我看了下最新的代码,其内部的核心机制并没有大的改动,只是增加了更多了校验性的健壮机制代码。
我把分享的内容整合了一下,再加上了一些期间漏掉的地方,整理成了这篇文章。

WPF

看了Vue的实现机制,不由地想起了以前写WPF的时候,MVVM的思想也是一脉相承的。记得后来转前端,第一次看红宝书的时候,看到Object.defineProperty这个api的时候,就觉得利用这个,可以实现一套类似于WPF的MVVM框架,没想到真的被Vue实现了。(^_^)

WPF是一门专注于展示层的技术,它在深层次上帮助程序员把思维的重心固定在了逻辑层,让展示层永远处于逻辑层的从属地位。而它具备这种能力的关键是它引入了Data Binding概念以及与之配套的Dependency Property系统和Data Template

Data Binding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student : INotifyPropertyChanged  
{
private string name;

public string Name
{
get { return name; }
set
{
name = value;
if (PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

8325303181Data Template

1
2
3
4
5
6
7
8
9
<Window x:Class="WpfApplication1.MainWindow"
xmlns="/schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="/schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="185,43,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="209,96,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>

截取了两端以前写过的WPF代码,等等可以看到这个实现上和Vue的实现还是有很多共同之处的。

正文

MVVM原理浅析

初始化一个Vue实例的重中之重就是建立model和view之间的联系。

  1. Observer: 通过observer对data进行监听,即利用Object.defineProperty对该data的数据存取符getter和setter拦截其取值和赋值
  2. Compiler: 将template进行编译,提取其指令和占位符,进行初始值渲染,并订阅watcher
  3. Watcher: 关联Observer和Compiler, 把compiler提取出的指令和占位符中的数据依赖订阅在对应属性的observer上,同时触发相关依赖的视图更新方法

801-599-4360Observer

  • 数据监听模块
  • 通过Object.defineProperty设置getter,setter数据劫持,从而监听所有对象属性的变化
  • 为每个属性新建一个发布者dep实例,来作为存放对应的watcher
  • 当数据发生变化时,触发dep中相应的方法来通知watcher

利用Object.defineProperty实现的数据劫持,也是Vue双向绑定的核心所在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 设置数据存取符getter和setter的具体方法,实现属性数据劫持
*/
Observer.prototype.defineReactive = function(data, key, val) {
let dep = new Dep() / 新增一个发布者
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if(Dep.target) { / 如果当前存在一个target watcher
dep.depend() / 则将这个dep实例加入到target watcher中
}
return val
},
set(newVal) {
if(newVal === val) {
return
}
val = newVal
dep.notify() / 通知所有已订阅的watcher进行更新
}
})
}

7072583357Dep

  • 发布者
  • 其中的subs数组存放着所有订阅这个属性的watcher
  • getter中,通过dep.depend()给dep.subs数组增加对应的订阅watcher
  • setter中,触发更新,通过dep.notify()通知所有订阅的watcher进行update
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
let uid = 0; / 发布者id, 因为每个属性都是唯一的,每个发布者也是唯一的,用这个id来确保其唯一性

/**
* 发布者
*/
export default function Dep() {
this.id = uid++;
this.subs = []
}

/**
* 在运行时,每次只有一个目标订阅者watcher
*/
Dep.target = null

/**
* 增加一个订阅者watcher
*/
Dep.prototype.addSub = function(sub) {
this.subs.push(sub)
}

/**
* 把dep传入到target watcher中,比较depIds后再判定时候增加订阅
*/
Dep.prototype.depend = function() {
Dep.target.addDep(this)
}

/**
* 遍历触发已订阅的更新
*/
Dep.prototype.notify = function() {
this.subs.forEach(sub => {
sub.update() / 触发watcher的update方法,进行数据更新
})
}

Watcher

  • 模版数据订阅模块
  • 订阅compiler中解析出来的dom的数据变化
  • 处理数据变化,调用updater进行页面数据更新
    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
    40
    41
    42
    43
    44
    export default function Watcher(vm, exp, cb) {
    this.vm = vm
    this.exp = exp / compiler中解析到的表达式的属性,如{{text}}中text
    this.cb = cb / 数据更新回调函数
    this.depIds = {} / 依赖对象id,防止重复
    this.value = this.get()
    }

    /**
    * 获取vm中对应exp的值
    */
    Watcher.prototype.get = function() {
    Dep.target = this
    / observer中已经定义了对象属性的get,这里因为已经设置过了一次代理
    / 等同于访问this.vm.$options.data[this.exp]的值
    let value = this.vm[this.exp] / 此处触发了observer中声明的get
    Dep.target = null
    return value
    }

    /**
    * 更新数据
    */
    Watcher.prototype.update = function() {
    let val = this.get()
    let oldVal = this.value
    if(val !== oldVal) {
    this.value = val
    this.cb.call(this.vm, val)
    }
    }

    /**
    * 发布者dep增加新的watcher订阅者
    */
    Watcher.prototype.addDep = function(dep) {
    / Observer中每定义一个属性,就会创建一个dep实例,属性和dep实例是一一对应的
    / 假如当前watcher的depIds中不存在该dep的id,则说明该属性是新属性,需要对该发布者dep进行订阅
    / 通过depIds保证了每个watcher只会添加进每个发布者dep的subs订阅数组中一次,确保了每个watcher只会订阅每个dep一次
    if (!this.depIds.hasOwnProperty(dep.id)) {
    dep.addSub(this)
    this.depIds[dep.id] = dep
    }
    }

Compiler

  • 编译模块(parser解析模块)
  • 解析dom, 提取出需要绑定的数据,如,为其设置对应的watcher
  • 同时触发第一次更新
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
export default function Compiler(el, vm, watcher) {
this.$vm = vm
this.$el = document.querySelector(el)
this.$watcher = watcher

if(this.$el) {
this._initCompile(this.$el)
}
}

Compiler.prototype._initCompile = function(el) {
let childNodes = el.childNodes || []

/ 循环遍历子节点
/ 如果是文本节点,则对文本内容进行正则替换
/ 如果是元素节点,先对其属性节点进行编译操作,然后再递归其子节点
toRealArray(childNodes).forEach(node => {
let textContent = node.textContent,
attributes = node.attributes

if(isTextNode(node)) {
if(trim(textContent)) {
this._compileTextContent(node, trim(textContent))
}
}

if(isElementNode(node)) {
if(attributes) {
this._compileAttributes(node, attributes)
}
this._initCompile(node)
}
})
}

/ 编译文本节点
Compiler.prototype._compileTextContent = function(node, textContent) {
let reg = /\{\{(.*)\}\}/

if(reg.test(textContent)) {
let expOrFn = RegExp.$1
bindWatcher(node, this.$vm, expOrFn, Updater.text)
}
}

/ 编译属性节点
Compiler.prototype._compileAttributes = function(node, attributes) {
toRealArray(attributes).forEach(attr => {
let name = attr.name,
value = attr.value,
dirReg = /^fv\-(.*)$/,
onReg = /^@(.*)$/

if(dirReg.test(name)) { / 指令
let dir = RegExp.$1
let expOrFn = value

Dirs[dir](node, this.$vm, expOrFn)
}

if(onReg.test(name)) { / 事件
let eventName = RegExp.$1
let expOrFn = value

bindEventHandler(node, eventName, this.$vm, expOrFn)
}
})
}

/ 指令集合,如fv-model, fv-show
const Dirs = {
model(node, vm, expOrFn) {
bindWatcher(node, vm, expOrFn, Updater.model)

let value = getValue(vm, expOrFn)
node.addEventListener('input', event => {
let newValue = event.target.value;
if (value === newValue) {
return;
}
setValue(vm, expOrFn, newValue);
value = newValue;
});
},
show(node, vm, expOrFn) {
bindWatcher(node, vm, expOrFn, Updater.show)
}
}

/ dom和watcher关联
function bindWatcher(node, vm, expOrFn, updater) {
updater(node, getValue(vm, expOrFn)) / 触发首次数据替换
new Watcher(vm, expOrFn, function(val){ / 为其设置相应的watcher
updater(node, val)
})
}

/ 为node绑定事件
function bindEventHandler(node, eventName, vm, expOrFn) {
let fn = vm.$methods[expOrFn]
node.addEventListener(eventName, fn.bind(vm))
}

function getValue(vm, expOrFn) {
return vm.$options.data[expOrFn]
}

function setValue(vm, expOrFn, value) {
vm.$options.data[expOrFn] = value
}

简单实现的updater,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default {
text(node, value) {
let reg = /\{\{(.*)\}\}/
if(reg.test(value)) {
node.textContent = node.textContent.replace(reg, value)
} else {
node.textContent = value
}
},
model(node, value) {
node.value = value
},
show(node, value) {
if(!value) {
node.style.visibility = 'hidden'
} else {
node.style.visibility = 'visible'
}
}
}

instance

  • 创建vm实例
  • 首先调用observer进行所有数据监听
  • 然后调用compiler模块进行首次编译解析刷新,并设定相应watcher
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
40
41
42
43
44
45
46
47
48
49
let uid = 0

export default function FakeVue (options) {
this._uid = uid++
this.$options = options
this.$methods = options.methods
this._watch = options.watch
this._data = options.data
this._el = document.querySelector(options.el)
this._ob = observe(options.data) / 监听对象的每个属性
this._proxy()
new Compiler(options.el, this) / 解析dom, 订阅watcher并触发首次页面更新
}

/ 代理,把数据和函数代理到vm上
FakeVue.prototype._proxy = function() {
this._data &&
Object.keys(this._data).forEach(key => {
this._dataProxy(key)
})
this.$methods &&
Object.keys(this.$methods).forEach(fnName => {
this[fnName] = this.$methods[fnName]
})
this._watch &&
Object.keys(this._watch).forEach(watchExp => {
this.$watch(watchExp, this._watch[watchExp])
}) / 必须在observe后调用
}

/ 将_data中的数据代理到vm上,方便this直接调用
/ vm.prop === vm._data.prop
FakeVue.prototype._dataProxy = function (key) {
let self = this
Object.defineProperty(self, key, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return self._data[key]
},
set: function proxySetter(val) {
self._data[key] = val
}
})
}

FakeVue.prototype.$watch = function(exp, cb) {
new Watcher(this, exp, cb)
}

总结

  • 整体而言,Vue利用Object.defineProperty进行了数据劫持
  • 采用观察者(发布-订阅)模式,发布由每个属性对应的dep实例来完成,watcher负责订阅dep,当接收到dep发出的notify()时,及时调用update()来更新数据。
  • 949-272-0601

819-573-9114运行结果

See the Pen FakeVue by ForeverSc (@ForeverSc) on gauge knife.

写在最后

因为只是早期几个commit的代码,后面会顺着时间线慢慢往后更新…

EOF

5627084469

801-426-7436目标效果

用将width和height设置为0,通过border特性来实现三角形的效果的方法,想必很多人都实现过。
但是如果要求三角形边缘具有阴影效果,这样一来以前的实现方式显然是不行,因为box-shadow属性对border内部的边缘是没有效果的。
今天公司的交互图上正好要实现这种效果,经过一番搜寻和思考,最终通过一种在原来的方式上稍作升级的方式实现了这一效果。
实现的关键就是给border-top和border-right两个三角形同时设置颜色,将两个小三角形融合成一个大的三角形,然后通过transform进行旋转操作,将盒模型原本的两条边作为三角形的腰,对外展示,再对其设置box-shadow效果,实现阴影效果。

具体实现

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>CSS实现带阴影效果的三角形</title>
<style>
.box {
position: relative;
width: 600px;
height: 400px;
background: #fff;
border: 1px solid #ccc;
box-shadow: 2px 2px 2px #ccc;
}
.box:after {
position: absolute;
display: inline-block;
top: 380px;
left: 300px;
width: 0;
height: 0px;
content: '';
border-style: solid;
border-width: 20px;
border-color: #fff #fff transparent transparent;
transform: rotate(135deg);
box-shadow: 2px -2px 2px #ccc;
}
</style>
</head>
<body>
<div class="box">
</div>
</body>
</html>

运行效果

See the Pen Grid by ForeverSc (@ForeverSc) on (403) 279-8309.

动态作用域和词法作用域

总结

事实上JavaScript 并不具有动态作用域。它只有词法作用域,简单明了。不过this机制某种程度上很像动态作用域。

6476679367两者的区别

  • 词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定
    的(this 也是!所以才说this很像动态作用域)。

  • 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。

  • 词法作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查询。而动态域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,到函数调用时的环境中查。

1
2
3
4
5
6
7
8
var a = 1
function test(str) {
console.log(a)
}
!function(){
var a = 2
test() / 输出的是1,假如是动态作用域这里应该输出2
}()

我们可以用eval来欺骗作用域,实现一种假象的动态作用域,也正是因为这种不可预料性,所以不推荐使用eval, 动态作用域往往会导致很多的问题。

1
2
3
4
5
6
7
8
var a = 1
function test(str) {
eval(str)
console.log(a)
}
!function(){
test('var a = 2')
}()

(845) 239-5961运行结果

See the Pen 动态作用域和词法作用域 by ForeverSc (800-277-2609) on 914-537-5715.

(207) 307-9056参考链接

整理

知识整理:

  • 利用getComputedStyle获取当前元素最终使用的CSS的值。
  • 关于new操作符的理解
  • 深拷贝
  • 清除浮动时的zoom:1

如何获取元素最终使用的CSS的值

通过style属性可以帮助我们获取css的值,但是这只能获取内联样式的属性,不能获取样式表中的数据。但jQuery中的css()函数可以用来获取当前css的属性,那么是怎么实现的呢?其实就是通过getComputedStyle来获取当前元素最终使用的CSS的值。
var style = window.getComputedStyle(element[, pseudoElt]);
然后通过getPropertyValue()来获取属性值

1
2
var elem = document.getElementById("elem-container");
var theCSSprop =window.getComputedStyle(elem,null).getPropertyValue("height");

IE中有个特有的属性currentStyle具有类似的效果。

ps:这里有个小问题,之前在写一个手风琴的效果时,我想通过获取div块的高度来判定div是否已经显示,css中将div的display设为none,height,width设为300px,这时候再通过getComputedStyle获取height时发现,获取的height竟然还是300px,虽然此时页面并没有显示这个div,可见在获取属性值时还是存在一些小问题,最后我通过offsetHeight来获取时就可以获取到正确的值。两者在实现方面还有待探讨,希望有大神告知。


关于new操作符的理解

new的过程拆分成以下三步:
(1) var p={}; 也就是说,初始化一个对象p
(2) p.__proto__ = Person.prototype;
(3) Person.call(p); 也就是说构造p,也可以称之为初始化p
关键在于第二步,我们来证明一下:

1
2
3
var Person = function(){};
var p = new Person();
alert(p.__proto__ === Person.prototype);/true

这段代码会返回true。说明我们步骤2是正确的。
先说最重要的一句话,实例中的指针仅仅指向原型,不指向构造函数!!
先看个例子:

1
2
3
4
function F(){};
var instance=new F();
console.log(instance.constructor);/  function F(){}
console.log(F.prototype.constructor);/function F(){}

两个的输出都是构造函数F,这个时候是不是觉得奇怪,实例instance.constructor既然有值,那么不就是说实例中有指针指向构造函数F么?
其实不然,这里instance.constructor之所以会有值,不是因为它有指针,而是它的原型对象F.prototype中有指向构造函数F的指针。当实例中没有这个属性的时候,就会沿着原型链向上查找,从而在它的原型对象中 找到了这个constructor属性。因此,不要被instance.constructor有值这一点欺骗了。

其实所有的问题就集中在这个语句上,归结到词就是new的操作到底是怎么样的。

1
2
3
Sub.prototype=new Super();
Sub.prototype={};
Sub.prototype=new Object();

第一句是继承时原型链的写法,第二句是构造对象时使用的原型语法。
后者的写法会导致原型对象被完全重写,导致其constructor指向了Object,这点是JavaScript高级程序设计p155上说明的。
但第一句中,Sub.prototype这个原型对象的constructor又指向了哪里呢,书上并没有给出答案,我自己做了个实验

1
2
3
4
5
6
7
8
9
10
function Super(){
}
function Sub(){
}
Sub.prototype=new Super();
var instance=new Sub();
console.log(Sub.prototype.constructor);/function Super(){}
console.log(instance.constructor);/function Super(){}
console.log(Sub.prototype.hasOwnProperty("constructor"));/false Sub.prototype中并没有这个属性
console.log(Super.prototype.hasOwnProperty("constructor"));/true Super.prototype中才有

结合了原型语法p155和原型链p163两个部分,对原型链有了一个更加深入的认识。
结果让人感到惊讶,Sub.prototype中竟然没有constructor属性,但是它在调用constructor属性的时候却指向了Super,那么这个constructor属性来自哪里呢?
其实这个constructor是它沿着原型链向上寻找到的,也就是Super.prototype中的。
那么这一点有何上面的Sub.prototype={};有何关系呢?其实Sub.prototype={};就等于Sub.prototype= new Object();
这样一替换实际上一切就很明显了,Object代替了Super的位置,所以Sub.prototype中也不存在constructor,这个constructor是来自Object.prototype中的constructor。
结论:
只要是重写(或者=new …)原型对象,它的属性就会消失(包括constructor),这样它就会沿着原型链向上找到另外一个原型对象的constructor

ps:只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
Object.prototype.isPrototypeOf(instance)/true
Super.prototype.isPrototypeOf(instance)/true


深拷贝

1
2
3
4
5
6
7
8
 function deepClone(target){
  /适用于对象中有Array,function,json
      var result=(!target.sort)?{}:[];/解决会把Array复制成Object的问题
        for(var key in target){
            result[key]=typeof target[key]==='object'?deepClone(target[key]):target[key];
        }
      return result;
}

732-797-3540清除浮动时的zoom:1

1
2
3
4
5
6
7
8
9
10
11
12
13
.float{
      float:left;
      width:200px;
      height:200px;
      background:red;

    }
    .container{
      border: 1px solid #ccc;
      background: #fcf;
      zoom:1;
      overflow: hidden;
    }

之前一直没有注意为何要加上一个zoom:1,直接overflow:hidden不就可以满足BFC的要求清除浮动了么?为何要多次一举呢?
今天才发现在ie的低版本中ie<6中,overflow是无效的,只有通过设置zoom:1触发ie独有的hasLayout属性才行
所以在清除浮动时为了兼容版本的问题,还是overflow和zoom都加上为好

EOF

318-994-7551

(907) 874-9695记一些琐碎的知识点

几个经常记错的东西:

  1. concat不会影响数组本身,而会返回一个新的数组(slice,map,reduce也不会影响数组本身),concat老是打错…
  2. slice(begin,end)包括begin,但不包括end,例如slice(0,arr.length)并没有截取最后一位。
  3. reduce()注意参a数的顺序prev curr index array。
  4. setTimeout注意out小写
  5. (new Date).getMonth();/注意这个月份是从0-11的所以要加上1;
    (new Date).getTime() 和 +new Date()都返回当前时间的long型毫秒数,获取当前时间直接用(new Date).toLocaleString()即可
  6. 字符串’0’,空数组[],空对象{}都返回true
  7. arguments不是真正意义上的数组,所以要先进行转换才可运用Array中的方法,可以使用Array.prototype.slice.call(arguments)转换arguments数组
  8. (123).toString(2) / "1111011",parseInt((123).toString(2),2) /123
    注意parseInt(,2)的含义是把字符串当作二进制字符串进行解析,所以想把一个二进制字符串转化为十进制要用parseInt(str,2)而不是parseInt(str,10)
  9. HTML几个常用的转义字符&nbsp; &lt; &gt; &amp; &quto
    curation
  10. JavaScipt中的所有数字都是64位的双精度浮点数,由三部分组成,符号占1位,指数部分占11位小数,部分占52位
  11. 跨域,只要端口,域名,协议有一个不同就是跨域
  12. event.keyCode == 13 回车键的keycode是13 可以利用这点来实现回车登录功能

这周主要遇到的问题:

  • 纯CSS实现三角形:
    1
    2
    3
    4
    5
    6
    7
    8
    demo {
      width: 0;
      height: 0;
      background: #ccc;/*加上背景图就知道原理了*/
      border-width: 20px;
      border-style: solid;
      border-color: transparent transparent red transparent;
    }

之前没遇到过将border-width设置成很大的情况,所以也就不清楚border到底是怎么划分的,实际上当border-width为一个较大数值的时候可以发现四周的border是四个梯形,此时将中间的内容部分设置为空,将其他三条边设置成transparent就变成了一个三角形,十分巧妙。

  • 对于对象和基本数据类型的理解
    1
    2
    var booltest1=new Boolean("false");
    var booltest2=Boolean("false");

注意两者的区别,1其实还是Object类型,而2则为Boolean型
所以if(booltest1){}是可以继续的,不要被欺骗了。

  • Object.keys();方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。

    1
    2
    3
    var data = {a: 1, b: 2, c: 3, d: 4}; Object.keys(data).filter(function(x) { 
    return data[x]>2;/注意是属性,需要data[x]
    });
  • 动态添加大量DOM节点可以使用createDocumentFragment()

    1
    2
    3
    4
    5
    6
    7
            var fragment=document.createDocumentFragment();
             var ulList=document.createElement("ul");
              for(var i=0;i<100;i++){
                  var liNode=document.createElement("li");
                  fragment.appendChild(liNode);
              }
              ulList.appendChild(fragment);

同时注意insertBefore()的用法
oldnode.parent.insertBefore(newElement,oldnode);

  • URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符。URL是URI的子集,两者的之间:一般来说(URL)统一资源定位符,可以提供找到该资源的路径,URI标识了一个资源但是不一定给了资源的路径,但是URL给出了如何找到该资源的路径。一句话解释:URI 和 URL 都定义了 what the resource is。URL 还定义了 how to get the resource。任何东西,只要能够唯一地标识出来,都可以说这个标识是 URI 。如果这个标识是一个可获取到上述对象的路径,那么同时它也可以是一个 URL ;但如果这个标识不提供获取到对象的路径,那么它就必然不是 URL

    1
    2
    3
    4
    encodeURI("/www.baidu.com/")/一般对整个链接使用
    /"/www.baidu.com/"
    encodeURIComponent("/www.baidu.com/");/一般对部分URL参数使用6
    /"https%3A%2F%2Fwww.baidu.com%2F"
  • Ajax,true异步,false同步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

                var xhr = null;
                if (window.XMLHttpRequest) {
                    xhr = new XMLHttpRequest();
                } else if (window.ActiveXObject) {
                    xhr = new ActiveXObject("Microsoft.XMLHttp");
                }
                if (xhr !== null) {
                    xhr.onreadystatechange = function() {

                        docPara.innerHTML = xhr.responseText;
                    };
                    xhr.open("GET", "test.txt", true);
                    xhr.send(null);
                }
  • 取cookie为abc的值

    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
    var CookieUtil={
            get:function(name){
                var cookieName=encodeURIComponent(name)+"=",
                    cookieStart=document.cookie.indexOf(cookieName),
                    cookieValue=null;
                if(cookieStart>-1){
                    var cookieEnd=document.cookie.indexOf(";",cookieStart);
                    if(cookieEnd==-1){
                        cookieEnd=document.cookie.length;
                    }
                    cookieValue=decodeURIComponent
                    (document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
                }
                return cookieValue;
            },
            set:function(name,value,expires,path,domain,secure){
                var cookieText=encodeURIComponent(name)+"="+encodeURIComponent(value);
                if(expires instanceof Date){
                    cookieText+="; expires="+expires.toGMTString();
                }
                if(path){
                    cookieText+="; path="+path;
                }
                if(domain){
                    cookieText+="; domain="+domain;
                }
                if(secure){
                    cookieText+="; secure=";
                }
                document.cookie=cookieText;

            },
            unset:function(name,path,domain,secure){
                this.set(name,"",new Date(0),path,domain,secure);
            }


        }
  • 判断浏览器是否为chrome

    1
    var isChrome=navigator.userAgent.indexOf("Chrome")!==-1;
  • 判断URL参数是否存在

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function getQueryStringArgs(){
                /取得查询字符串并去掉开头的问号
            var qs=(location.search.length>0?location.search.substring(1),""),
                /保存数据对象
                args={},
                /取得每一项
                items=qs.length?qs.split("&"):[],
                item=null,
                name=null,
                value=null,
                i=0,
                len=items.length;
            for(i=0;i<len;i++){
                item=items[i].split("=");
                name=decodeURIComponent(item[0]);
                value=decodeURIComponent(item[1]);
                if(name.length){
                    args[name]=value;
                }
            }
            return args;
        }

eof

z-index获取问题

var z-index=$(this).css(“z-index”);
alert(typeof z-index)/string注意这是个string类型!!!
z-index+=10;/所以这里加上10会在字符串后加上10,变成1010;所以z-index的增加速度就惊人了。
这里直接使用
$(this).css(“z-index”,”+=10”);即可

其他通过css获取的数值也要测试下
顺便整理下
字符串+数字得到的也就是字符串
字符串-数字 得到的是数字

在不确定的情况下还是先统一转换以免出错

height 的百分比是按照父类的高度的,不管父类是什么布局,没有影xiang
top百分比也是按照父类的高度进行的,不管父类是什么布局,没有影响。

如果一个元素 absolute,它是相对谁说的:
如果它的父元素是非 static 值(这里自己说了 static则相对父元素;否则相对是非 static 值的父元素的父元素……直到找到一个最近的非 static 值的祖先元素。如果都没有,则是相对 window 而言。

首先,我想告诉你的是,如果父级元素是绝对定位(absolute)或者没有设置,里面的绝对定位(absolute)自动以body定位。这句话是错的。
正确的是:只要父级元素设了position并且不是static(默认既是static),那么设定了absolute的子元素即以此为包含块(最近的)。
绝对定位(Absolute positioning)元素定位的参照物是其包含块,既相对于其包含块进行定位,不一定是其父元素。
建议去详细通读一下定位体系和包含块。

New obj=new obj();注意这个验证一下

(单选题)
var myObject ={
foo:”bar”,
func:function(){
varself=this;
console.log(this.foo);
console.log(self.foo);
(function(){
console.log(this.foo);
console.log(self.foo);
}());
}};
myObject.func();
程序的输出是什么?

问题整理:

  1. 字符串+数字?结果是个字符串,还是数字?
  2. 如果一个元素absolute,那么它是相对于谁说的?其父元素?
  3. var o=new Object;这个创建对象的方法对么?
  4. 当一个对象的内部函数中含有一个立即执行函数,那么这个立即执行函数中的this指向谁?

解惑:

  1. 这个问题是前几天写下面这段代码的时候遇到的:
    1
    2
    3
    4
    var z-index=$(this).css("z-index");/这里是获取元素的z-index属性值
    alert(typeof z-index)/输出string 问题就在这里,注意这是个string类型!!!
    z-index+=10;/假如此时的值是"10",其实就是"10"+10,变成"1010";
    $(this).css("z-index","+=10");/这里直接使用即可

发生这个问题,主要有两个点我没有注意到:

  • $().css这个方法获取的值是一个string类型,而我误认为是number类型了,所以后再进行加减运算前要先进行一次检验,或者统一转换成相同的数据类型在进行运算。
  • 另外就是我对字符串+数字=字符串,字符串-数字=数字等等之间的关系了解的不深。(+”1”==1 通过+运算符转换数据类型)
    ps:这里正好在附上一点小问题,关于string类型和字符串对象之间的一个问题。
    1
    2
    3
    4
    var text1="1";
    var text2=new String("1");
    console.log(typeof text1);/string
    console.log(typeof text2);/object

注意两者使用typeof获取类型时得到的结果是不一样的。

2.第二个问题是关于absolute理解方面的,之前自己理解的不够深,用起来的时候也没怎么关注,这次终于彻底搞清楚了。
一个absolute元素,如果它的父元素的position不是static(即默认的定位),那么这个absolute元素的top,left,width,height等就是相对于其父元素的,而假如其父元素是默认的定位,那么就需要向上寻找父元素的父元素,一直找到定位不是默认的祖先元素为止,知道body。

3.直接使用var obj=new Object;省略()定义一个对象是可以的,和加上括号是一样的效果,如果不给构造函数传递参数的情况下,这对括号是可以省略的,但是不推荐这样做。(《JavaScript高级程序设计》p35,还是书上的内容,又被自己忽略了…)

4.最后一个问题是由下面这段代码引起的:

1
2
3
4
5
6
7
8
9
10
11
12
var myObject ={
    foo:"bar",
    func:function(){
        var self=this;
        console.log(this.foo); /bar  
        console.log(self.foo); /bar  
        (function(){
            console.log(this.foo);/undefined   
            console.log(self.foo);/bar   
        }());
    }};
myObject.func();

其他几个还好理解,就是第三个console.log(this.foo)的结果有点出乎我的意料,原本以为这个this也会像self一样,沿着作用域链向上寻找,结果却不是这样,这个this直接指向了window。因为这是个立即执行函数,它并没有指定调用对象,就相当于在全局中直接调用这个函数,所以this直接就指向了window。


更新一点关于_proto_指向问题的理解:
前几天看了这篇博文[410-537-7225
(/www.cnblogs.com/onepixel/p/5024903.html)总感觉写的有些怪怪的。

1
2
3
4
5
function f(){}
f.prototype.foo = "abc";
console.log(f.foo); /undefined
var obj = new f();
console.log(obj.foo); /abc

就是第一个输出undefined的理解上,prototype中的属性对f构造函数而言是不可见的。

其中对于\_proto_的理解也让我感到迷惑,通过在网上的搜索,我总结了一下。

\_proto_=constructor.prototype重点记住这个等式
每个对象都有\_proto_属性,指向它的构造函数的原型对象。
f.\_proto_=Function.prototype 先求f的析构函数为Function,然后再求其原型对象即可
obj.\_proto_=Object 先求obj.constructor为f 然后再求f.prototye为Object


最后加点吐槽:
这周真是累爆了,投实习简历,看书,上课,写代码,堆积的任务实在太多了,感觉还有好多知识点没有学到,网上看了看大神们的面经,感觉还差的好远好远…

eof

连等赋值underscore源码中的内容

昨天在阅读underscore源码的时候突然发现了一个连等式:
exports = module.exports = _;
想了想,觉得有点不确定自己的想法,就上网搜了下,没想到网上竟然也激烈讨论过这个问题。
先看下这段代码:

1
2
3
4
5
var a = {n:1};  
var b = a;   
a.x = a = {n:2};  
alert(a.x);/ --> undefined  
alert(b.x);/ --> {n:2}

开始看到这两个结果的时候我感觉很不可思议,b.x是{n:2}还可以理解,可a.x怎么会变成了undefined。
然后我看了replanter上的回答,前两个回答的意思基本上是一样的,.运算符的优先级是高于=号的,其实也就是说解释器会先找到a和a.x的指针,如果有指针则不改变它们,没有就创建一个指针,指向null,然后再将指针都指向了{n:2}
这样实际上,a.x中的a所指向的对象和a指向的对象并不是一个对象,a.x中的a实际指向的是{n:1},而a指向的对象是{n:2},并不存在a.x,因为b指向的是之前那个{n:1}的对象,所以b.x相当于{n:1}.x,而{n:1}.x已经指向了{n:2},所以结果如上。

1
2
3
4
5
var a = {n:1};  
var b = a;   
a = a.x = {n:2};  
alert(a.x);/ --> undefined  
alert(b.x);/ --> {n:2}

就算改成a=a.x={n:2}得到的结果也是一样的,原因如上。


后来我又看了另一个论坛的讨论帖/www.iteye.com/topic/785445
其中clue和R大的回答给了我很大的启发

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
开始分析a.b = a = {n:2}这个表达式,先假设{n:1}这个对象为OBJ1,{n:2}为OBJ2,全局为GLOBAL。 

它的解析如下: 
a.b = Expression1 
Expression1为另一个赋值表达式: 
a = {} 

首先计算a.b = Expression1,按(3)中赋值表达式运行步骤 
step1先得到引用(OBJ1, "b") 
step2解析Expression1{ 
   Expression1解析 
   step1得到引用(GLOBAL, "a") 
   step2得到一个对象OBJ2 
   step3取值,仍是OBJ2 
   step4将引用(GLOBAL, "a")赋值为step3结果 
   step5返回OBJ2 
} 
step3取值,结果同样为OBJ2 
step4将(OBJ1, "b")赋值为OBJ2 
step5返回OBJ2 

最终结果: 
OBJ1: {n:1, b:OBJ2} 
OBJ2: {n:2} 
a : OBJ2

这是clue的回答的一部分,最重要的一点就是step1先得到引用(OBJ1, "b")这个步骤,按照我之前的想法,这一个取得引用应该是在最后执行的,其实不然,它反而是第一个执行的,这也就与之前SegmentFlaut中所说的.运算符的优先级要高于=达成了一致。


R大的回答更加深入,其实问题的关键就是:

运算符结合性(associativity)与求值顺序(order of evaluation)的概念分不清楚时容易弄错的问题。
结合性和求值顺序是没有必然关系的。
JavaScript的表达式的求值顺序都是从左向右的。赋值运算的结合性虽然是右结合,但同样是从左向右求值的

更加详细的部分可以看这里:7817859160


最后记一个错误,301-679-4515主贴的最后加了一道题,有点画蛇添足的感觉。

1
2
3
4
5
6
function fun(){
    var a = b = 5;
}
fun();
alert(typeof a); / --> undefined
alert(typeof b); / --> number

其实这里的结果和之前讨论的连等没有半毛钱关系,还容易混淆视听,b之所以变成全局变量完全就是因为它在函数中并没有进行声明,所以会自动变成全局变量,所以才会出现上面的结果,这里只要将代码改成如下:

1
2
3
4
5
6
7
function fun(){
    var b;
    var a = b = 5;
}
fun();
alert(typeof a); / --> undefined
alert(typeof b); / --> undefined

结果就显而易见了。

butter packer

Hello World