JavaScript变量作用域

所谓作用域,也就是变量和函数起作用的区域,不同的语言有着不同的实现。而在JavaScript中,这也是往往让人迷糊的地方,也是JavaScript中必须理解的特性之一。

首先来看看下面的代码:

1
2
3
4
5
6
7
8
9
for (var i = 0; i < 10; i++) {
var foo = 'bar';
}

function test() {
console.log(i); // 10
console.log(foo); // 'bar'
}
test();

从运行结果不难看出,变量i在循环体结束之后仍然可以访问。而诸如Java、C#等语言中,循环结束之后便不能再访问到循环体中的变量了。继续看下面代码:

1
2
3
4
5
6
7
8
9
var name = 'heroic';

function foo() {
var name = 'foo';
console.log(name); // 'foo'
}

foo();
console.log(name); // 'heroic'

结合两段代码可以知道,在JavaScript中是不存在块级作用域的,只存在函数作用域(也称“本地作用域”)和全局作用域。而作用域中的变量我们分别称其为:局部变量和全局变量。

前辈们(前端的长辈们,嚯嚯)常常反复在说,全局变量是魔鬼啊,魔鬼啊。。。(回声)那到底是嘛原因呢?咱接着往下说。

阅读全文 »

关于《JavaScript Web Application》中的bind方法

去年底就草草了了的翻过《JavaScript Web Application》这本书,当时就遇到好几个地方没怎么看明白,当时也没有去深究,就只是简单的过一遍这本书。由于此前JavaScript方面的知识匮乏,所以把这本书就缓在阅读队列里了,最近才轮到。其间读了好几本基础和进阶的书,所以这次读这本书,很多之前不明白的地方就豁然明了了。不过好记性不如烂笔头,没准下次忘记了呢。

记得此前书中对ES5 bind()方法的实现就是一个让我感到迷糊的地方(中文版P16):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (!Function.prototype.bind) {
Function.prototype.bind = function (context) {
var slice = [].slice
, args = slice.call(arguments, 1)
, self = this
, nop = function () {} // ① `nop`(函数)的作用?
, bound = function () {
// ② 为什么要做`instanceof`判断?
return self.apply(this instanceof nop ? this : (context || {}),
args.concat(slice.call(arguments)));
};

// ③ 为什么要设置它俩的原型?
nop.prototype = self.prototype;
bound.prototype = new nop();

return bound;
};
}

bind():用来动态改变函数调用时其内部this对象的引用,使目标函数基于正确的上下文进行调用。jQuery中实现为$.proxy()方法,ES5则自带了。

上面代码中① ② ③都是我此前不明白的地方,不过在这次阅读的过程中,就豁然开朗了。

阅读全文 »

Mongodb中随机的查询文档记录

在实际应用场景中,几乎都会有随机获取数据记录的需求。而这个需求在Mongodb却不是很好实现,就目前而言,大致上有三种解决方案:

  1. 先计算出一个从0到记录总数之间的随机数,然后采用skip(yourRandomNumber)方法。
  2. 为每一条记录增设random字段,插入数据时赋值为Math.random(),查询时采用$gte$lte
  3. 借助Mongodb对地理空间索引(geospatial indexes)的支持,从而可以在第二种方法的基础上来实现随机记录的获取。

因为Mongodb是不建议使用skip方法的,所以这里就略去第一种方法吧。

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala0...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala1...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala2...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala3...' })
> db.twitter.save({ username: 'heroic', random: Math.random(), content: 'balabala4...' })
/* more records... */

/* create index */
> db.twitter.ensureIndex({ username: 1, random: 1 })

> rand = Math.random()
> result = db.twitter.findOne({ username: 'heroic', random: { $gte: rand } })
> if (result == null) {
> result = db.twitter.findOne({ username: 'heroic', random: { $lte: rand } })
> }

方法三

1
2
3
4
5
6
7
8
9
10
11
> db.twitter.save({ username: 'heroic', random: [Math.random(), 0], content: 'balabala0...' })
> db.twitter.save({ username: 'heroic', random: [Math.random(), 0], content: 'balabala1...' })
> db.twitter.save({ username: 'heroic', random: [Math.random(), 0], content: 'balabala2...' })
> db.twitter.save({ username: 'heroic', random: [Math.random(), 0], content: 'balabala3...' })
> db.twitter.save({ username: 'heroic', random: [Math.random(), 0], content: 'balabala4...' })
/* more records... */

/* create index */
> db.twitter.ensureIndex({ username: 1, random: '2d' })

> result = db.twitter.findOne({ username: 'heroic', random: { $near: [Math.random(), 0] } })

更多关于Mongodb地理空间索引资料,请参见这里

目前这几种方案似乎都不是很理想,但是也没有其他办法了,所以广大程序员们就相约到Mongodb的官方jira提了相应的需求,但是目前仍然没有任何的响应。可以参见这里,围观一下。

关于JavaScript中的继承(三):再谈类式继承

《关于JavaScript中的继承(一):类式继承》中已经基本上实现了类式继承,但仍然还存在一些问题,接下来对之前的实现进一步进行完善。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Parent = function (name) {
this.name = name || 'heroic';
};
Parent.prototype.print = function () {
console.log('name: ', this.name);
};

var parent = new Parent();

var Child = function (name) {
Parent.apply(this, arguments);
};
Child.prototype = parent;
Child.prototype.setChildAge = function (age) {
this.age = age;
};

parent.setChildAge(10);
console.log(parent.age); // 10

正如代码所见,在为子对象原型添加自己独有方法的时候,父对象也受到了影响,这可不是期望的结果。

1
2
3
4
5
var child = new Child('child');
console.log(child.name); // 'child'

delete child.name;
console.log(child.name); // 'heroic'

同样如代码所示,child对象持有了两个name属性,一个是通过构造函数拷贝的,另一个是原型链上的,当删除掉本身的name属性后,便访问到了原型链上的了。对于这个问题,解决方案很简单直接:

1
2
3
4
5
/* ... */
Child.prototype = Parent.prototype; // 只继承父对象原型链上的属性
/* ... */
delete child.name;
console.log(child.name); // undefined

但是这种方法也并没有解决最开始的那个问题,即添加或删除子对象原型上的属性时,会一并反映到父对象中。这个时候就需要用到《关于JavaScript中的继承(二):原型式继承》中用到的临时构造函数了。

阅读全文 »

关于JavaScript中的继承(二):原型式继承

JavaScript中本没有类,所以凡事也不要强求,强扭的瓜总是不填的嘛。继承也无非就是一个对象拥有另外一个对象的特性,所以完全不需要复杂的去模拟面向对象中的类式继承,而借助JavaScript独特的原型机制就可以实现了。同样,需要创建一个对象时也不再采用模拟类-对象的方式,而是直接使用JavaScript的对象字面量就好了。因为对于对象来说我们关系的无非也就是它具备哪些属性、有哪些行为而已。

1
2
3
4
5
6
7
8
9
var parent = {
printName: function () {
console.log(this.name || 'parent');
}
};

var child = {}; // 暂时这样
child.name = 'child'; // 在很多情况下我们并不希望继承原对象自己的属性,而是在我们需要时,直接添加就好
child.printName(); // 重点的是我们希望child具备打印自己名字的行为

上面的代码目前还不能工作,上一篇文章中谈到过JavaScript中对象属性查找机制,如果child对象本身没有printName()这个方法,那我们保证其原型上有这个方法,它就能正常工作了。

1
2
3
4
5
6
7
8
9
10
11
12
/* var parent... */

var object = function (obj) {
var F = function () {};
F.prototype = obj;
return new F;
};

// 改变child对象的创建方式,不直接采用字面量了,而是用`object`方法来创建
var child = object(parent);
child.name = 'child';
child.printName(); // 'child'

通过一个object()函数,我们就完全可以实现一个对象从另一个对象继承了。其原理很简单,即在object()函数内部创建一个临时的构造函数,然后修改这个构造函数的原型,最后再返回这个构造函数的一个实例。

阅读全文 »