自己平时使用过的一些mongodb命令记录

根据日期统计出某些手机号记录数

1
2
3
4
5
6
7
8
9
10
var data = db.spinetmp.aggregate([
{$match: {status: 'visable', checkuserid: {$in: ['156****5285', '156****5194', '137****8875', '158****1125']}}},
{$project: {mobile: '$checkuserid', date: {$dateToString: {format: "%Y-%m-%d", date: "$version" }}}},
{$group: {_id: {mobile: '$mobile', date: "$date"}, count: {$sum: 1}}},
{$sort: {'_id.mobile': 1, '_id.date': -1}}
]);
// 将数据转为csv格式
data.map(function(item) {
return item._id.mobile + ',' + item._id.date + ',' + item.count;
}).join("\n");

倒出用户书单的描述和书的推荐语

1
2
3
4
5
6
7
8
9
10
11
12
var users = db.user.find({mobile_number: {$in: ['156****6041', '155****4511', '136****5641']}}, {_id: 0, mobile_number: 1, user_id: 1});

users.map(function(item) {
item.floors = db.floor.find({user_id: item.user_id, type: {$exists: false}}, {user_id: 1, name: 1, desc: 1, item_list: 1}).toArray();
item.floors = item.floors.map(function(item) {
item.item_list = item.item_list.map(function(item) {
return db.item.findOne({bid: item}, {bid: 1, title: 1, remark: 1});
});
return item;
});
return item;
});

根据出版社和书排出用户收藏最多的书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rs.slaveOk();
var data = db.library.aggregate([
{$group: {_id: {publisher: '$publisher', bid: '$bid', title: '$title'}, count: {$sum: 1}}}
]);

// to csv
data
.toArray()
.sort(function(a, b) {
return b.count - a.count;
})
.map(function(item) {
return item._id.publisher + ',' + item._id.bid + ',' + item._id.title + ',' + item.count;
})
.join("\n");

查出指定出版社前10名的书籍

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
rs.slaveOk();
var publishers = [
'人民文学出版社',
'上海译文出版社',
'中华书局',
'中信出版社',
'商务印书馆',
'生活·读书·新知三联书店',
'广西师范大学出版社',
'译林出版社',
'新星出版社',
'南海出版公司'
];
var data = db.library.aggregate([
{$match: {publisher: {$in: publishers}}},
{$group: {_id: {publisher: '$publisher', title: '$title'}, count: {$sum: 1}}}
]);

//findIndex是es6的函数,自己写。。。
var findIndex = function(fn) {
var i = 0, r;
while (this.length > i && !(r = fn(this[i], i, this))) i++;
return r ? i : -1;
};

// to csv
data
// 对象数组化
.toArray()
// 按出版社归类排序
.sort(function(a, b) {
return (publishers.indexOf(a._id.publisher) - publishers.indexOf(b._id.publisher)) || (b.count - a.count);
})
// 过滤出前10的书籍,这里可以用另一个数组对象来存储数据,减少运算步骤。
.filter(function(item, index, ary) {
return index < (findIndex.call(ary, function(data) {
return item._id.publisher === data._id.publisher;
}) + 10);
})
// 格式化
.map(function(item) {
return item._id.publisher + ',' + item._id.title + ',' + item.count;
})
// toString
.join("\n");

ifNull的使用

1
db.library.aggregate([
	{$match: {user_id: '763532fec962ca3f5c96cd024860a43a'}},
	{$project: {read_status: {$ifNull: ["$read_status", "unread"]}}},
	{$group: {_id: '$read_status', total: {$sum: 1}}}
]);

javascript的一些常用技能

反转一个字符串

1
'123456789'.split('').revert().join(''); // '987654321';

去重

1
'11122213434'.split('').filter((item, index, ary) => index == ary.indexOf(item)).join(''); // '1234'

多维数组->字符串

1
[[[1], 2], [3], 4].join(); // '1,2,3,4'

返回随机元素

1
2
3
4
5
6
// method1
let ary = ['male', 'female'];
let ret = ary[~~(Math.random() * ary.length)];

// method2
[1, 2, 3, 4, 5, 6].sort(() => {var v = Math.random() - .5; return v / Math.abs(v);}).shift();

计算出字符串中字符出现次数

1
'abcdaabc'.split('').reduce((p, c) => (p[c] = (p[c] || 0) + 1, p) , {});

找出一段文本中长度为l,重复次数超过n的子文本集合

1
2
3
4
5
6
7
function(text, l, n) {
var f = 0, ts = {}, t;
while((t = text.substr(f++, l)) && t.length == l) {
ts[t] = ts[t] ? ++ts[t] : 1;
}
return Object.keys(ts).filter((k) => ts[k] >= n);
}

OrientDB学习笔记(一)

下载、安装和启动

下载地址http://orientdb.com/download/,下载后解压进入bin目录,执行./server.sh命令,搞定。

你可以通过http://localhost:2480访问web地址,也可以执行`./console.sh`启动客户端。

What

OrientDB是一款开源的非关系数据库系统,使用Java实现的,他是一个符合模型的数据库,支持文档、图和键值三种模型。记录间的关系以直接联系的方式存储在图数据库。

特质

支持ACID事务保证。
图数据库的管理,采用Apache的TinkerPop开源图计算框架。

Schema Class Cluster Record

msgpack使用记录

我们平时做接口调接口,总会遇到一些接口返回的数据量特别庞大的问题,这容易导致客户端流量增加,也影响响应时间。msgpack可以将数据压缩,通过msgpack,我们一般可以讲原始数据压缩一半左右,当数量很大时,非常赞!
步骤也很简单,如下:
1.安装模块

1
npm install msgpack5 --save

2.压缩/解压

1
let msgpack = require('msgpack5')()
  , encode  = msgpack.encode
  , decode  = msgpack.decode;

console.log(decode(encode({ 'hello': 'world' }))) // { hello: 'world' }

encode的结果是Buffer对象,你可以直接返回二进制编码,也可以通过toString(‘hex’)返回十六进制编码。

1
// 返回二进制
this.body = encode({ 'hello': 'world' });
// 返回十六进制
this.body = encode({ 'hello': 'world' }).toString('hex');

如果你用二进制,用request测试,记得必须将encoding设为null

1
request({
	method: 'post',
	url:'http://localhost:3000/sign_in', 
	form: {account: '11111121123', password: '123456'},
	encoding: null
}, function(err, res, body) {
	if (err) throw err;
	console.log(msgpack.decode(body));
})

如果你用十六进制,需要将结果转为二进制后才能正常解码。

1
request({
	method: 'post',
	url:'http://localhost:3000/sign_in', 
	form: {account: '11111121123', password: '123456'},
}, function(err, res, body) {
	if (err) throw err;
	console.log(msgpack.decode(Buffer(body, 'hex')));
})

注意 对数据进行encode操作的时候,如果encode的内容是类似于mongoose返回的实体对象这一类的数据的话,这个对象是存在某些读写限制的,encode遇到这类数据后抛出not implemented yet的异常。如果是mongoose的实体对象,可以使用其内置的toObject方法将对象转换为普通对象再执行encode操作。

highlight使用前的构建操作

highlight是一个很好的代码高亮工具,但是官方似乎没有太好的使用说明,以至于一开始使用的时候搞了有点久,这里做个简单的记录。
1.你需要引入css文件,这个很简单,样式文件都在src/styles下面
2.如果你跟我一样是使用bower安装的话,一开始是没有highlight.pack.js文件的,你需要build一下

1
// 假设你只需要javascript代码的高亮
node tools/build.js javascript

3.之后你会看到根目录下面多了一个build的文件夹,打开就会发现highlight.pack.js文件了,引入该文件,再按官方指引就可以正常使用了。

redis基础操作指令

预置数据

1
127.0.0.1:6379> sadd user1:follow 'user2'
(integer) 1
127.0.0.1:6379> sadd user1:follow 'user3'
(integer) 1
127.0.0.1:6379> sadd user1:follow 'user5'
(integer) 1
127.0.0.1:6379> smembers user1:follow
1) "user5"
2) "user3"
3) "user2"
127.0.0.1:6379> sadd user2:follow 'user1'
(integer) 1
127.0.0.1:6379> sadd user2:follow 'user4'
(integer) 1
127.0.0.1:6379> sadd user2:follow 'user5'
(integer) 1
127.0.0.1:6379> smembers user2:follow
1) "user5"
2) "user1"
3) "user4"

删除集合中的某个元素(SREM)

1
127.0.0.1:6379> smembers user4:follow
1) "user1"
2) "user4"
127.0.0.1:6379> srem user4:follow user3
(integer) 0
127.0.0.1:6379> srem user4:follow user1
(integer) 1
127.0.0.1:6379> smembers user4:follow
1) "user4"
127.0.0.1:6379>

计算出两个集合的交集(SINTER)

1
127.0.0.1:6379> sinter user1:follow user2:follow
1) "user5"

将一个元素从一个集合移动到另一个集合(SMOVE)

1
127.0.0.1:6379> smove user2:follow user1:follow 'user6'
(integer) 0
127.0.0.1:6379> smembers user1:follow
1) "user5"
2) "user3"
3) "user2"
127.0.0.1:6379> smembers user2:follow
1) "user5"
2) "user1"
3) "user4"
127.0.0.1:6379> smove user2:follow user1:follow 'user5'
(integer) 1
127.0.0.1:6379> smembers user1:follow
1) "user5"
2) "user3"
3) "user2"
127.0.0.1:6379> smembers user2:follow
1) "user1"
2) "user4"
127.0.0.1:6379> smove user2:follow user1:follow 'user4'
(integer) 1
127.0.0.1:6379> smembers user1:follow
1) "user5"
2) "user3"
3) "user2"
4) "user4"
127.0.0.1:6379> smembers user2:follow
1) "user1"
127.0.0.1:6379> sinter user1:follow user2:follow
(empty list or set)

返回所有集合的并集(SUNION)

1
127.0.0.1:6379> sunion user1:follow user2:follow
1) "user3"
2) "user5"
3) "user2"
4) "user4"
5) "user1"

返回集合的基数(SCARD)

1
127.0.0.1:6379> scard user1:follow
(integer) 4
127.0.0.1:6379> scard user2:follow
(integer) 1

将两个集合的交集成为另一个key的值(SINTERSTORE)

1
127.0.0.1:6379> sadd user3:follow 'user2'
(integer) 1
127.0.0.1:6379> sadd user3:follow 'user4'
(integer) 1
127.0.0.1:6379> smembers user3:follow
1) "user2"
2) "user4"
127.0.0.1:6379> sinterstore user4:follow user1:follow user3:follow
(integer) 2
127.0.0.1:6379> smembers user4:follow
1) "user2"
2) "user4"

移除集合中的一个随机元素(SPOP)

1
127.0.0.1:6379> spop user1:follow
"user4"
127.0.0.1:6379> smembers user1:follow
1) "user5"
2) "user3"
3) "user2"
127.0.0.1:6379> spop user1:follow
"user5"
127.0.0.1:6379> smembers user1:follow
1) "user3"
2) "user2"

将集合的并集作为另一个键的值(SUNIONSTORE)

1
127.0.0.1:6379> sunionstore user5:follow user1:follow user2:follow user3:follow
(integer) 4
127.0.0.1:6379> smembers user5:follow
1) "user3"
2) "user1"
3) "user2"
4) "user4"

计算两个集合的差集(SDIFF)

1
127.0.0.1:6379> smembers user1:follow
1) "user3"
2) "user2"
127.0.0.1:6379> sdiff user5:follow user1:follow
1) "user1"
2) "user4"

判断一个元素是否为某个集合内的元素(ISMEMBER)

1
127.0.0.1:6379> smembers user1:follow
1) "user3"
2) "user2"
127.0.0.1:6379> sismember user1:follow user1
(integer) 0
127.0.0.1:6379> sismember user1:follow user3
(integer) 1

返回集合中的一个随机元素(SRANDMEMBER)

1
127.0.0.1:6379> srandmember user1:follow
"user2"

计算两个集合的差集并将结果作为值赋值给一个键(SDIFFSTORE)

1
127.0.0.1:6379> smembers user1:follow
1) "user3"
2) "user2"
127.0.0.1:6379> smembers user5:follow
1) "user3"
2) "user1"
3) "user2"
4) "user4"
127.0.0.1:6379> smembers user4:follow
1) "user2"
2) "user4"
127.0.0.1:6379> sdiffstore user4:follow user5:follow user1:follow
(integer) 2
127.0.0.1:6379> smembers user4:follow
1) "user1"
2) "user4"

自己写的一些常用函数

随机字符串函数

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
/**
* 生成随机字符串
*
* @param total 字符串长度
* @param method 字符串类型,数字/小写字母/大写字母
* @return string
*/

var randomString = function(total, method) {
var codes = [], ret = '';
method = method || 7;
if (method & randomString.number) {
var number_codes = new Array(10).fill(0).map(function(item, index) {return 48 + index;});
codes = codes.concat(number_codes);
}
if (method & randomString.lower) {
var number_codes = new Array(26).fill(0).map(function(item, index) {return 97 + index;});
codes = codes.concat(number_codes);
}
if (method & randomString.upper) {
var number_codes = new Array(26).fill(0).map(function(item, index) {return 65 + index;});
codes = codes.concat(number_codes);
}
while (total--) {
ret += String.fromCharCode(codes[Math.floor(Math.random() * codes.length)]);
}
return ret;
};
/**
* 数字
*/

randomString.number = 1;
/**
* 小写字母
*/

randomString.lower = 2;
/**
* 大写字母
*/

randomString.upper = 4;

继承函数

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
var extend = function() {
var params = Array.prototype.slice.call(arguments),
method = 'number' == typeof params[params.length - 1] ? params.pop() : 0,
ret = method & extend.replaceFirst ? params.shift() : {},
deep = method & extend.deepExtend,
rmNull = method & extend.removeNull,
obj;

while (params.length) {
obj = params.shift();
for (var k in obj) {
if (null === obj[k]) {
rmNull ? delete ret[k] : ret[k] = obj[k];
} else if (!ret[k] || ret[k].constructor !== Object || obj[k].constructor !== Object) {
ret[k] = obj[k];
} else {
ret[k] = deep ? extend(ret[k], obj[k], method) : obj[k];
}
}
}
return ret;
};
/**
* 将结果赋值给第一个对象
*/

extend.replaceFirst = extend.RF = 1;
/**
* 深度合并
*/

extend.deepExtend = extend.DE = 2;
/**
* 删除值为null的键
*/

extend.removeNull = extend.RN = 4;

字符串及时间格式化函数

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
/**
* 字符串格式化方法
* @params null
* @example _.format('我是{0}', 'CoderQ') => '我是CoderQ'; '我是{name}'.format({name:'CoderQ'}) => '我是CoderQ';
* @depends null
* @return string
*/

var string_format = function() {
var args = Array.prototype.slice.call(arguments),
string = args.shift(),
params = 'object' == typeof args[0] ? args[0] : args;
return string.replace(/\{(\w+)(\|([^}]+))?\}/g, function($, $1, $2, $3) {
return 'undefined' == typeof params[$1] ? eval($3) : params[$1];
});
};

/**
* 时间格式化函数
* @params
* format 格式
* language 语言
* @example new Date().format('Y-m-d H:i:s')
* @depends toString
* @return string
*/

var date_format = function(format, date, lang) {
var _format = format || '';
var _lang = lang || 'zh';
var _month_text = {
'zh': ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'],
'en': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
};
var _day_text = {
'zh': ['日', '一', '二', '三', '四', '五', '六'],
'en': ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat']
};
var _year = date.getFullYear();
var _month = date.getMonth() + 1;
var _date = date.getDate();
var _day = date.getDay();
var _hour = date.getHours();
var _minute = date.getMinutes();
var _second = date.getSeconds();
return _format.replace(/Y/g, _year)
.replace(/y/g, _year.toString().substr(2))
.replace(/M/g, _month_text[_lang][_month - 1])
.replace(/m/g, _month > 9 ? _month : '0' + _month)
.replace(/n/g, _month)
.replace(/D/g, _day_text[_lang][_day])
.replace(/d/g, _date > 9 ? _date : '0' + _date)
.replace(/j/g, _date)
.replace(/H/g, _hour > 9 ? _hour : '0' + _hour)
.replace(/G/g, _hour)
.replace(/h/g, (_hour % 12) > 9 ? (_hour % 12) : '0' + (_hour % 12))
.replace(/g/g, _hour % 12)
.replace(/i/g, _minute > 9 ? _minute : '0' + _minute)
.replace(/s/g, _second > 9 ? _second : '0' + _second);
};

/**
* 数据格式化
* @param format
* @param obj
* @return string
*/

var format = function(format, obj) {
if (Object.prototype.toString.call(obj) == '[object Date]') {
return date_format.apply(null, arguments);
} else {
return string_format.apply(null, arguments);
}
};

实现记录微信分享路径树的功能

背景

接运营要求,希望记录文章被某个人分享出去之后的路径树,但因为微信分享时是无法获取到用户信息的,所以能记录的信息只有原始分享用户以及他分享出去后的路径树。

实现方法

当文章被分享出去时,我们的URL里记录有文章ID及用户ID,当页面被打开时,后端会生成一个share_id,以query的方式加到当前URL的后面,当页面被再次分享出去时,后端会根据这个share_id再生成一个下级的share_id,并记录他们之间的关系,而之前的share_id在数据库里的记录会被更新为已读。

微信签名生成规则

签名的生成需要以下四个参数

  • jsapi_ticket 先通过appId获取access_token,在通过access_token获取jsapi_ticket
  • noncestr 随机字符串
  • timestamp 时间戳(秒级)
  • url 当前页面URL,是当前页面URL,而不是要分享出去的URL

最后形成一段与此类似的字符串

1
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

在经过sha1加密,得到signature

数据模型

1
2
3
4
5
6
7
8
9
var WeixinShareLogSchema = new Schema({
_id: {type: String, unique: true, required: true},
parent_id: {type: String, ref: 'weixin_share_log'},
article_id: {type: ObjectId, ref: 'article', required: true},
is_read: {type: Boolean, default: false},
created_by : {type : String, required: true},
created_at : {type : Date, default : Date.now}
});
通过这种结构,我们只要搜索created_by再加上前端的异步实现就能知道被该用户分享出去的文章的树状结构了。

后端实现

因项目历史原因,没有使用es6

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
......
// 分享ID,如果是第一次分享,则为用户ID
var parent_id = this.request.query.share_id || '';
// 生成一个下级分享ID
var share_id = md5(parent_id + Date.now());
var WeixinShareLogModel = mongoose.model('weixinsharelog');
// 将当前的分享ID置为已读
var e_weixin_share_log = yield WeixinShareLogModel.findOne({_id: parent_id}).exec();
if (e_weixin_share_log) {
e_weixin_share_log.is_read = true;
yield e_weixin_share_log.save();
}
e_weixin_share_log = new WeixinShareLogModel({
_id: share_id,
parent_id: parent_id,
article_id: article_id,
created_by: user_id
});
yield e_weixin_share_log.save();
// url定义微信要求的用于获取签名的地址,除#号之后的剩余部分
var url = Tools.format('{protocol}://{host}{url}', {
protocol: config.protocol,
host: config.host,
url: this.url.split('#').shift()
});
// 获取微信签名
var sign = yield Tools.getWeixinSignature(url);
// 生成下级分享地址
var share_url = Tools.format('{url}?share_id={share_id}', {
url: url.split('?').shift(),
share_id: share_id
});
// ......

获取微信签名

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
/**
* 获取微信签名
*/

var getWeixinSignature = function* (url) {
function* getWeixinTicket() {
var data = yield request.get(format(config.weixin.path.access_token, config.weixin));
return yield request.get(format(config.weixin.path.ticket, {access_token: data.access_token}));
}
try {
var data, expire, nonce_str, timestamp, jsapi_ticket, signature;
// 如果过期时间不存在或仅存200秒,则重新获取jsapi_ticket,并重置过期时间
if (!getWeixinSignature.expire || getWeixinSignature.expire > new Date() - 200 * 1000) {
data = yield getWeixinTicket();
getWeixinSignature.expire = data.expires_in * 1000 + new Date().getTime();
getWeixinSignature.jsapi_ticket = data.ticket;
}
if (!getWeixinSignature.jsapi_ticket) return false;
nonce_str = randomString(16);
timestamp = Math.floor(new Date().getTime() / 1000);
signature = sha1(format("jsapi_ticket={jsapi_ticket}&noncestr={nonce_str}&timestamp={timestamp}&url={url}", {
jsapi_ticket: getWeixinSignature.jsapi_ticket,
nonce_str: nonce_str,
timestamp: timestamp,
url: url
}));
return {
appId: config.weixin.app_id,
timestamp: timestamp,
nonceStr: nonce_str,
signature: signature
}
} catch (e) {
console.log(e.stack);
return false;
}
};

前端配置

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
wx.config({
debug: false,
appId: '<%= sign.appId %>',
nonceStr: '<%= sign.nonceStr %>',
timestamp: '<%= sign.timestamp %>',
signature: '<%= sign.signature %>',
jsApiList: [
"onMenuShareTimeline",
"onMenuShareAppMessage",
"onMenuShareQQ",
"onMenuShareWeibo",
"hideOptionMenu",
"showOptionMenu"
]
});
wx.ready(function() {
var desc = $('meta[name=description]').attr('content') || $('body').find('h1,h2,h3,h4,p').eq(0).text() || '美丽阅读文章分享';
var image_url = $('body').find('img').get(0).src;
var shareInfo = {
title: '<%= article_title %>',
desc: desc,
imgUrl: image_url,
link: '<%= share_url %>'
};
/*分享到朋友圈*/
wx.onMenuShareTimeline(shareInfo);
//获取“分享给朋友”按钮点击状态及自定义分享内容接口
wx.onMenuShareAppMessage(shareInfo);
//获取“分享到QQ”按钮点击状态及自定义分享内容接口
wx.onMenuShareQQ(shareInfo);
//获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
wx.onMenuShareWeibo(shareInfo);
});

具体JSSDK配置请参考微信官方文档

Javascript继承之理解__proto__、prototype、constructor三者的含义与用法

如何写出没有污染的继承

1.javascript中一切都是对象,每个对象都是基于原型对象创建的,每个对象中都有proto属性,这个属性指向的就是它基于的原型对象。

1
2
3
4
5
6
var Foo = function () {};
var foo = new Foo();
console.log(
Foo.__proto__ === Function.prototype, // true
foo.__proto__ === Foo.prototype, // true
);

2.只有构造函数对象才有prototype属性,构造函数的作用是创建对象,创建对象的时候,它要知道这个对象基于哪个原型来创建,这个prototype指向的就是这个原型。

1
2
var Foo = function () {};
// Foo.prototype 指向 Foo.prototype;

3.只有原型对象才有constructor属性,而且也是系统(浏览器)给创建的。前面说过构造函数对象的prototype属性会指向一个原型对象,那么这个原型对象中的constructor属性指向的就是这个构造函数。

1
2
3
4
5
var Foo = function () {};
var foo = new Foo();
console.log(
foo.constructor === Foo, // true
);

4.思想合并

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
var Parent = function () {
this.name = 'parent';
};
Parent.prototype.sayName = function () {
alert(this.name);
};

var Son = function () {
Parent.apply(this, arguments);
this.name = 'son';
};
Son.prototype.__proto__ = Parent.prototype;

var parent = new Parent();
var son = new Son();
parent.sayName();
son.sayName();

console.log(
Parent.__proto__ === Function.prototype,
parent.__proto__ === Parent.prototype,
parent.constructor === Parent,
Son.__proto__ === Function.prototype,
son.__proto__ === Son.prototype,
son.constructor === Son
);

参考资料

,