tomoat的笔记


  • 首页

  • 归档

  • 标签

JavaScript中的内存释放

发表于 2017-03-10

内容来自网络 http://www.jianshu.com/p/3b7946c4b118

一、如何查找当前作用域的上级作用域

1
2
3
4
5
6
7
8
9
let num = 20;
function fn() {
let num = 200;
return function () {
console.log(num);
};
}
const f = fn();
f(); // 输出 200

以上代码fn中返回了一个函数,用f去接收这个返回的函数,然后再执行f(),最后输出的是200,刚接触的同学可能会有疑问,为什么在全局作用域下执行的f()输出的num不是全局作用域中的20,而是fn函数的私有作用域中的200!

上级作用域查找规则:看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁,和函数哪里执行没有任何的关系。

以上的代码在全局的变量和函数进行预解析之后,执行fn函数,fn函数又进行预解析形成自己的私有作用域,然后执行fn函数中的代码,最后返回一个函数,该函数被f接收。当f执行的时候,有一行代码输出num:console.log(num),根据作用域搜索规则,首先在自己的作用域中找,没有找到,然后再到上级的作用域中查找,根据作用域查找的规则,只看当前函数在哪个作用域下定义的,所以f函数的上级作用域是fn。

img

​ 如何查找上级作用域.png

有了以上的基础之后,再看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let num = 20;
function fn() {
let num = 200;
return function () {
console.log(num);
};
}
const f = fn();
f(); // 输出 200
~function () {
var num = 2000;
f(); // 输出什么呢?
}();
// 等价于
~(function () {
let num = 2000
f()
}())

加上了一个自执行函数,我们知道自执行函数是有自己的作用域的,但是此时f函数执行,依然输出200。 要时刻记住上级作用域的查找规则:只看当前函数在哪个作用域下定义的。

这时候很多同学可能会疑惑了,我们不是讲解JavaScript的内存释放的嘛?怎么还讲起了作用域的内容,稍安勿躁…在JavaScript的内存释放中要用到这些知识呢!

二、堆内存的释放

对象数据类型或者函数类型在定义的时候,首先都会开辟一个堆内存,堆内存有一个引用地址,如果外面有引用这个地址,我们就说这个内存被占用了,就不能销毁了。

1
2
var obj1 = {name:"iceamn"};
var obj2 = obj1;

​ img

​ 堆内存.png

如果想要让堆内存释放(销毁),只需要把所有引用它的变量赋值为null即可,如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁。也就是说,在上面的那种感觉情况下,只有把obj1和同obj2都置为null之后,0xff11这块对堆内存才会被释放,只要还有变量引用0xff11这块内存,它就不会释放。

三、栈内存的释放

3.1、全局作用域

在全局作用域下,只有当页面关闭的时候,全局作用域才会被销毁。

3.2、私有作用域

一般情况下,函数执行会形成一个新的私有作用域(在ES6之前只有函数执行才会产生私有作用域),当私有作用域中的代码执行完成后,当前作用域都会主动的进行释放和销毁。

不过依然有特殊的情况存在:当前私有作用域中的部分内容被作用域以外的东西占用了,那么当前作用域就不能销毁了。

3.2.1、 函数返回了一个引用数据类型的值(数组、函数…),并且该引用类型的值在函数的外面被一个其他变量接收了,这种情况下形成的私有作用域都不会销毁。

注意两个条件:
(1)函数返回引用数据类型的值;
(2)该引用类型的值在函数外面被一个其他变量接收了;

1
2
3
4
5
6
7
8
function fn() {
var num = 100;
return function () {
num ++;
console.log(num);
}
}
var f = fn(); // fn执行形成的作用域就不能再销毁了

注意:即使fn返回的函数中什么代码都没有,没有使用到fn私有作用域中的任何变量和函数,在以上情况下,fn的私有作用域也不会被销毁,即:

1
2
3
4
5
6
function fn() {
var num = 100;
return function () {
}
}
var f = fn();
3.2.2、 在一个私有作用域中,给DOM元素绑定方法,私有作用域不能被销毁
1
2
3
4
5
var btn = document.getElementById('btn1');
~function () {
btn.onclick = function () {
}
}();

在自执行函数中形成了一个私有的作用域,在这个私有作用域中为页面上的一个button元素绑定了点击事件,所以这个私有作用域也不能被销毁。

img

​ 自执行函数的情况.png

3.2.3、 “不立即销毁”
1
2
3
4
5
6
function fn() {
var num = 100;
return function () {
}
}
fn()(); // 首先执行fn,返回一个小函数对应的内存地址,然后紧接着让返回的小函数再执行

以上代码就是“不立即销毁”的情况,fn返回的函数没有被其他的任何变量占用,但是还需要执行一次,所以暂时不能销毁,但返回的值执行完成后,浏览器会在空闲的时候把它销毁了。

还记得一开始介绍的上级作用域吗,我们再对那张图进行分析:

img

​ 上级作用域的情况.png

只要某作用域还有被引用,那么该作用域就不能被销毁,一旦没有任何变量引用了,该私有作用域就会被销毁了。

四、练习题


  • 第一题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function fn() {
    var i = 10;
    return function (n) {
    console.log(n + (++i));
    };
    }
    var f = fn();
    f(10); // 21
    f(20); // 32
    fn()(10); // 21
    fn()(20); // 31
  • 第二题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function fn(i) {
    return function (n) {
    console.log(n + i++);
    }
    }
    var f = fn(13);
    f(12);//->25
    f(14);//->28
    fn(15)(12);//->27
    fn(16)(13);//->29

75道程序员面试逻辑思维题

发表于 2017-03-09

【1】假设有一个池塘,里面有无穷多的水。现有2个空水壶,容积分别为5升和6升。问题是如何只用这2个水壶从池塘里取得3升的水。
【2】周雯的妈妈是豫林水泥厂的化验员。 一天,周雯来到化验室做作业。做完后想出去玩。 “等等,妈妈还要考你一个题目,”她接着说,”你看这6只做化验用的玻璃杯,前面3只盛满了水,后面3只是空的。你能只移动1只玻璃杯,就便盛满水的杯子和空杯子间隔起来 吗?” 爱动脑筋的周雯,是学校里有名的”小机灵”,她只想了一会儿就做到了。请你想想看,”小机灵”是怎样做的?
【3】三个小伙子同时爱上了一个姑娘,为了决定他们谁能娶这个姑娘,他们决定用手枪进行一次决斗。小李的命中率是30%,小黄比他好些,命中率是50%,最出色的枪手是小林,他从不失误,命中率是100%。由于这个显而易见的事实,为公平起见,他们决定按这样的顺序:小李先开枪,小黄第二,小林最后。然后这样循环,直到他们只剩下一个人。那么这三个人中谁活下来的机会最大呢?他们都应该采取什么样的策略?

【4】一间囚房里关押着两个犯人。每天监狱都会为这间囚房提供一罐汤,让这两个犯人自己来分。起初,这两个人经常会发生争执,因为他们总是有人认为对方的汤比自己的多。后来他们找到了一个两全其美的办法:一个人分汤,让另一个人先选。于是争端就这么解决了。可是,现在这间囚房里又加进来一个新犯人,现在是三个人来分汤。必须寻找一个新的方法来维持他们之间的和平。该怎么办呢?
按:心理问题,不是逻辑问题
【5】在一张长方形的桌面上放了n个一样大小的圆形硬币。这些硬币中可能有一些不完全在桌面内,也可能有一些彼此重叠;当再多放一个硬币而它的圆心在桌面内时,新放的硬币便必定与原先某些硬币重叠。请证明整个桌面可以用4n个硬币完全覆盖
【6】一个球、一把长度大约是球的直径2/3长度的直尺.你怎样测出球的半径?方法很多,看看谁的比较巧妙
【7】五个大小相同的一元人民币硬币。要求两两相接触,应该怎么摆?

【8】猜牌问题
S 先生、P先生、Q先生他们知道桌子的抽屉里有16张扑克牌:红桃A、Q、4 黑桃J、8、4、2、7、3 草花K、Q、5、4、6 方块A、5。约翰教授从这16张牌中挑出一张牌来,并把这张牌的点数告诉 P先生,把这张牌的花色告诉Q先生。这时,约翰教授问P先生和Q 先生:你们能从已知的点数或花色中推知这张牌是什么牌吗? 于是,S先生听到如下的对话:P先生:我不知道这张牌。
Q先生:我知道你不知道这张牌。
P先生:现在我知道这张牌了。
Q先生:我也知道了。
听罢以上的对话,S先生想了一想之后,就正确地推出这张牌是什么牌。
请问:这张牌是什么牌?
【9】一个教授逻辑学的教授,有三个学生,而且三个学生均非常聪明!
一天教授给他们出了一个题,教授在每个人脑门上贴了一张纸条并告诉他们,每个人的纸条上都写了一个正整数,且某两个数的和等于第三个!(每个人可以看见另两个数,但看不见自己的)
教授问第一个学生:你能猜出自己的数吗?回答:不能,问第二个,不能,第三个,不能,再问第一个,不能,第二个,不能,第三个:我猜出来了,是144!教授很满意的笑了。请问您能猜出另外两个人的数吗?
【10】某城市发生了一起汽车撞人逃跑事件
该城市只有两种颜色的车,蓝色15% 绿色85%
事发时有一个人在现场看见了
他指证是蓝车
但是根据专家在现场分析,当时那种条件能看正确的可能性是80%
那么,肇事的车是蓝车的概率到底是多少?
【11】有一人有240公斤水,他想运往干旱地区赚钱。他每次最多携带60公斤,并且每前进一公里须耗水1公斤(均匀耗水)。假设水的价格在出发地为0,以后,与运输路程成正比,(即在10公里处为10元/公斤,在20公里处为20元/公斤……),又假设他必须安全返回,请问,他最多可赚多少钱?
【12】现在共有100匹马跟100块石头,马分3种,大型马;中型马跟小型马。其中一匹大马一次可以驮3块石头,中型马可以驮2块,而小型马2头可以驮一块石头。问需要多少匹大马,中型马跟小型马?(问题的关键是刚好必须是用完100匹马)
【13】1=5 2=15 3=215 4=2145 那么5=?
【14】有2n个人排队进电影院,票价是50美分。在这2n个人当中,其中n个人只有50美分,另外n个人有1美元(纸票子)。愚蠢的电影院开始卖票时1分钱也没有。
问: 有多少种排队方法 使得 每当一个拥有1美元买票时,电影院都有50美分找钱
注:
1美元=100美分
拥有1美元的人,拥有的是纸币,没法破成2个50美分
【15】一个人花8块钱买了一只鸡,9块钱卖掉了,然后他觉得不划算,花10块钱又买回来了,11块卖给另外一个人。问他赚了多少?
【16】有一种体育竞赛共含M个项目,有运动员A,B,C参加,在每一项目中,第一,第二,第三名分别的X,Y,Z分,其中X,Y,Z为正整数且X>Y>Z。最后A得22分,B与C均得9分,B在百米赛中取得第一。求M的值,并问在跳高中谁得第二名。

【17】前提:
1 有五栋五种颜色的房子
2 每一位房子的主人国籍都不同
3 这五个人每人只喝一种饮料,只抽一种牌子的香烟,只养一种宠物
4 没有人有相同的宠物,抽相同牌子的香烟,喝相同的饮料
提示:
1 英国人住在红房子里
2 瑞典人养了一条狗
3 丹麦人喝茶
4 绿房子在白房子左边
5 绿房子主人喝咖啡
6 抽PALLMALL烟的人养了一只鸟
7 黄房子主人抽DUNHILL烟
8 住在中间那间房子的人喝牛奶
9 挪威人住第一间房子
10抽混合烟的人住在养猫人的旁边
11养马人住在抽DUNHILL烟的人旁边
12抽BLUEMASTER烟的人喝啤酒
13德国人抽PRINCE烟
14挪威人住在蓝房子旁边
15抽混合烟的人的邻居喝矿泉水
问题是:谁养鱼???
【18】5个人来自不同地方,住不同房子,养不同动物,吸不同牌子香烟,喝不同饮料,喜欢不同食物。根据以下线索确定谁是养猫的人。
1. 红房子在蓝房子的右边,白房子的左边(不一定紧邻)
2. 黄房子的主人来自香港,而且他的房子不在最左边。
3. 爱吃比萨的人住在爱喝矿泉水的人的隔壁。
4. 来自北京的人爱喝茅台,住在来自上海的人的隔壁。
5. 吸希尔顿香烟的人住在养马人的右边隔壁。
6. 爱喝啤酒的人也爱吃鸡。
7. 绿房子的人养狗。
8. 爱吃面条的人住在养蛇人的隔壁。
9. 来自天津的人的邻居(紧邻)一个爱吃牛肉,另一个来自成都。
10.养鱼的人住在最右边的房子里。
11.吸万宝路香烟的人住在吸希尔顿香烟的人和吸“555”香烟的人的中间(紧邻)
12.红房子的人爱喝茶。
13.爱喝葡萄酒的人住在爱吃豆腐的人的右边隔壁。
14.吸红塔山香烟的人既不住在吸健牌香烟的人的隔壁,也不与来自上海的人相邻。
15.来自上海的人住在左数第二间房子里。
16.爱喝矿泉水的人住在最中间的房子里。
17.爱吃面条的人也爱喝葡萄酒。
18.吸“555”香烟的人比吸希尔顿香烟的人住的靠右
【19】斗地主附残局
地主手中牌2、K、Q、J、10、9、8、8、6、6、5、5、3、3、3、3、7、7、7、7
长工甲手中牌大王、小王、2、A、K、Q、J、10、Q、J、10、9、8、5、5、4、4
长工乙手中牌2、2、A、A、A、K、K、Q、J、10、9、9、8、6、6、4、4
三家都是明手,互知底牌。要求是:在三家都不打错牌的情况下,地主必须要么输要么赢。
问:哪方会赢?
【20】一楼到十楼的每层电梯门口都放着一颗钻石,钻石大小不一。你乘坐电梯从一楼到十楼,每层楼电梯门都会打开一次,只能拿一次钻石,问怎样才能拿到最大的一颗?
【21】 U2合唱团在17分钟内得赶到演唱会场,途中必需跨过一座桥,四个人从桥的同一端出发,你得帮助他们到达另一端,天色很暗,而他们只有一只手电筒。一次同时最多可以有两人一起过桥,而过桥的时候必须持有手电筒,所以就得有人把手电筒带来带去,来回桥两端。手电筒是不能用丢的方式来传递的。四个人的步行速度各不同,若两人同行则以较慢者的速度为准。Bono需花1分钟过桥,Edge需花2分钟过桥,Adam需花5分钟过桥,Larry需花10分钟过桥。他们要如何在17分钟内过桥呢?
【22】一个家庭有两个小孩,其中有一个是女孩,问另一个也是女孩的概率
(假定生男生女的概率一样)
【23】为什么下水道的盖子是圆的?
【24】有7克、2克砝码各一个,天平一只,如何只用这些物品三次将140克的盐分成50、90克各一份?
【25】芯片测试:有2k块芯片,已知好芯片比坏芯片多.请设计算法从其中找出一片
好芯片,说明你所用的比较次数上限.
其中:好芯片和其它芯片比较时,能正确给出另一块芯片是好还是坏.
坏芯片和其它芯片比较时,会随机的给出好或是坏。
【26】话说有十二个鸡蛋,有一个是坏的(重量与其余鸡蛋不同),现要求用天平称三次,称出哪个鸡蛋是坏的!
【27】100个人回答五道试题,有81人答对第一题,91人答对第二题,85人答对第三题,79人答对第四题,74人答对第五题,答对三道题或三道题以上的人算及格, 那么,在这100人中,至少有( )人及格。
【28】陈奕迅有首歌叫十年
吕珊有首歌叫3650夜
那现在问,十年可能有多少天?
【29】
1
1 1
2 1
1 2 1 1
1 1 1 2 2 1
下一行是什么?
【30】烧一根不均匀的绳要用一个小时,如何用它来判断半个小时?
烧一根不均匀的绳,从头烧到尾总共需要1个小时。现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢? (微软的笔试题)
【31】共有三类药,分别重1g,2g,3g,放到若干个瓶子中,现在能确定每个瓶子中只有其中一种药,且每瓶中的药片足够多,能只称一次就知道各个瓶子中都是盛的哪类药吗?
如果有4类药呢?5类呢?N类呢(N可数)?
如果是共有m个瓶子盛着n类药呢(m,n为正整数,药的质量各不相同但各种药的质量已知)?你能只称一次就知道每瓶的药是什么吗?
注:当然是有代价的,称过的药我们就不用了
【32】假设在桌上有三个密封的盒,一个盒中有2枚银币(1银币=10便士),一个盒中有2枚镍币(1镍币=5便士),还有一个盒中有1枚银币和1枚镍币。这些盒子被标上10便士、 15便士和20便士,但每个标签都是错误的。允许你从一个盒中拿出1枚硬币放在盒前,看到这枚硬币,你能否说出每个盒内装的东西呢?
【33】有一个大西瓜,用水果刀平整地切,总共切9刀,最多能切成多少份,最少能切成多少份?
主要是过程,结果并不是最重要的
【34】一个巨大的圆形水池,周围布满了老鼠洞。猫追老鼠到水池边,老鼠未来得及进洞就掉入水池里。猫继续沿水池边缘企图捉住老鼠(猫不入水)。已知V猫=4V鼠。问老鼠是否有办法摆脱猫的追逐?
【35】有三个桶,两个大的可装8斤的水,一个小的可装3斤的水,现在有16斤水装满了两大桶就是8斤的桶,小桶空着,如何把这16斤水分给4个人,每人4斤。没有其他任何工具,4人自备容器,分出去的水不可再要回来。
【36】从前有一位老钟表匠,为一个教堂装一只大钟。他年老眼花,把长短针装配错了,短针走的速度反而是长针的12倍。装配的时候是上午6点,他把短针指在“6 ”上,长针指在“12”上。老钟表匠装好就回家去了。人们看这钟一会儿7点,过了不一会儿就8点了,都很奇怪,立刻去找老钟表匠。等老钟表匠赶到,已经是下午7点多钟。他掏出怀表来一对,钟准确无误,疑心人们有意捉弄他,一生气就回去了。这钟还是8点、9点地跑,人们再去找钟表匠。老钟表匠第二天早晨8点多赶来用表一对,仍旧准确无误。 请你想一想,老钟表匠第一次对表的时候是7点几分?第二次对表又是8点几分?
【37】今有2匹马、3头牛和4只羊,它们各自的总价都不满10000文钱(古时的货币单位)。如果2匹马加上1头牛,或者3 头牛加上1只羊,或者4只羊加上1匹马,那么它们各自的总价都正好是10000文钱了。问:马、牛、羊的单价各是多少文钱?
【38】一天,harlan的店里来了一位顾客,挑了25元的货,顾客拿出100元,harlan没零钱找不开,就到隔壁飞白的店里把这100元换成零钱,回来给顾客找了75元零钱。过一会,飞白来找harlan,说刚才的是假钱,harlan马上给飞白换了张真钱,问harlan赔了多少钱?
【39】猴子爬绳
这道力学怪题乍看非常简单,可是据说它却使刘易斯.卡罗尔感到困惑。至于这道
怪题是否由这位因《爱丽丝漫游奇境记》而闻名的牛津大学数学专家提出来的,那就不
清楚了。总之,在一个不走运的时刻,他就下述问题征询人们的意见:
一根绳子穿过无摩擦力的滑轮,在其一端悬挂着一只10磅重的砝码,绳子的另一端
有只猴子,同砝码正好取得平衡。当猴子开始向上爬时,砝码将如何动作呢?
“真奇怪,”卡罗尔写道,”许多优秀的数学家给出了截然不同的答案。普赖斯认为砝
码将向上升,而且速度越来越快。克利夫顿(还有哈考特)则认为,砝码将以与猴子一样
的速度向上升起,然而桑普森却说,砝码将会向下降!”
一位杰出的机械工程师说”这不会比苍蝇在绳子上爬更起作用”,而一位科学家却认
为”砝码的上升或下降将取决于猴子 吃苹果速度的倒数”,然而还得从中求出猴子尾巴的
平方根。严肃地说,这道题目非常有趣,值得认真推敲。它很能说明趣题与力学问题之
间的紧密联系。
【40】两个空心球,大小及重量相同,但材料不同。一个是金,一个是铅。空心球表面图有相同颜色的油漆。现在要求在不破坏表面油漆的条件下用简易方法指出哪个是金的,哪个是铅的。
【41】有23枚硬币在桌上,10枚正面朝上。假设别人蒙住你的眼睛,而你的手又摸不出硬币的
反正面。让你用最好的方法把这些硬币分成两堆,每堆正面朝上的硬币个数相同。
【42】三个村庄A、B、C和三个城镇A、B、C坐落在如图所示的环形山内。
由于历史原因,只有同名的村与镇之间才有来往。为方便交通,他们
准备修铁路。问题是:如何在这个环形山内修三条铁路连通A村与A镇,
B村与B镇,C村与C镇。而这些铁路相互不能相交。(挖山洞、修立交
桥都不算,绝对是平面问题)。想出答案再想想这个题说明什么问题。
●●●●●●●●●C●●●●●●●●●●
● ●
● ●
● ●
● ●
● ●
● ●
A C B
● ● ●
● ● ●
● ● ●
● ● ●
● ● ●
● ● ●
● ● ●
●●●●●●●●●●●●●●●●●●●● 43】屋里三盏灯,屋外三个开关,一个开关仅控制一盏灯,屋外看不到屋里
怎样只进屋一次,就知道哪个开关控制哪盏灯?
四盏呢~
【44】2+7-2+7全部有火柴根组成,移动其中任何一根,答案要求为30
说明:因为书写问题作如下解释,2是由横折横三根组成,7是由横折两根组成
【45】5名海盗抢得了窖藏的100块金子,并打算瓜分这些战利品。这是一些讲民主的海盗(当然是他们自己特有的民主),他们的习惯
是按下面的方式进行分配:最厉害的一名海盗提出分配方案,然后所有的海盗(包
括提出方案者本人)就此方案进行表决。如果50%或更多的海盗赞同此方案,此方
案就获得通过并据此分配战利品。否则提出方案的海盗将被扔到海里,然后下一名
最厉害的海盗又重复上述过程。
所有的海盗都乐于看到他们的一位同伙被扔进海里,不过,如果让他们选择的
话,他们还是宁可得一笔现金。他们当然也不愿意自己被扔到海里。所有的海盗都
是有理性的,而且知道其他的海盗也是有理性的。此外,没有两名海盗是同等厉害
的——这些海盗按照完全由上到下的等级排好了座次,并且每个人都清楚自己和其
他所有人的等级。这些金块不能再分,也不允许几名海盗共有金块,因为任何海盗
都不相信他的同伙会遵守关于共享金块的安排。这是一伙每人都只为自己打算的海
盗。
最凶的一名海盗应当提出什么样的分配方案才能使他获得最多的金子呢?
【46】他们中谁的存活机率最大?
5个囚犯,分别按1-5号在装有100颗绿豆的麻袋抓绿豆,规定每人至少抓一颗,而抓得最多和最少的人将被处死,而且,他们之间不能交流,但在抓的时候,可以摸出剩下的豆子数。问他们中谁的存活几率最大?提示:
1,他们都是很聪明的人
2,他们的原则是先求保命,再去多杀人
3,100颗不必都分完
4,若有重复的情况,则也算最大或最小,一并处死
【47】有5只猴子在海边发现一堆桃子,决定第二天来平分.第二天清晨,第一只猴子最早来到,它左分右分分不开,就朝海里扔了一只,恰好可以分成5份,它拿上自己的一份走了.第 2,3,4,5只猴子也遇到同样的问题,采用了同样的方法,都是扔掉一只后,恰好可以分成5份.问这堆桃子至少有多少只?
【48】话说某天一艘海盗船被天下砸下来的一头牛给击中了,5个倒霉的家伙只好逃难到一个孤岛,发现岛上孤零零的,幸好有有棵椰子树,还有一只猴子!
大家把椰子全部采摘下来放在一起,但是天已经很晚了,所以就睡觉先.
晚上某个家伙悄悄的起床,悄悄的将椰子分成5份,结果发现多一个椰子,顺手就给了幸运的猴子,然后又悄悄的藏了一份,然后把剩下的椰子混在一起放回原处,最后还是悄悄滴回去睡觉了.
过了会儿,另一个家伙也悄悄的起床,悄悄的将剩下的椰子分成5份,结果发现多一个椰子,顺手就又给了幸运的猴子,然后又悄悄滴藏了一份,把剩下的椰子混在一起放回原处,最后还是悄悄滴回去睡觉了.
又过了一会 …
…
又过了一会 …
总之5个家伙都起床过,都做了一样的事情
早上大家都起床,各自心怀鬼胎的分椰子了,这个猴子还真不是一般的幸运,因为这次把椰子分成5分后居然还是多一个椰子,只好又给它了.
问题来了,这堆椰子最少有多少个?
【49】小明和小强都是张老师的学生,张老师的生日是M月N日,
2人都知道张老师的生日是下列10组中的一天,
张老师把M值告诉了小明,把N值告诉了小强,
张老师问他们知道他的生日是那一天吗?
3月4日 3月5日 3月8日
6月4日 6月7日
9月1日 9月5日
12月1日 12月2日 12月8日
小明说:如果我不知道的话,小强肯定也不知道
小强说:本来我也不知道,但是现在我知道了
小明说:哦,那我也知道了
请根据以上对话推断出张老师的生日是哪一天
【50】一逻辑学家误入某部落,被囚于牢狱,酋长欲意放行,他对逻辑学家说:“今有两门,一为自由,一为死亡,你可任意开启一门。现从两个战士中选择一人负责解答你所提的任何一个问题(Y/N),其中一个天性诚实,一人说谎成性,今后生死任你选择。”逻辑学家沉思片刻,即向一战士发问,然后开门从容离去。逻辑学家应如何发问?
【51】说从前啊,有一个富人,他有30个孩子,其中15个是已故的前妻所生,其余15个是继室所生,这后一个妇人很想让她自己所生的最年长的儿子继承财产,于是,有一天,他就向他说:”亲爱的丈夫啊,你就要老了,我们应该定下来谁将是你的继承人,让我们把我们的30个孩子排成一个圆圈,从他们中的一个数起,每逢到10就让那个孩子站出去,直到最后剩下哪个孩子,哪个孩子就继承你的财产吧!”富人一想,我靠,这个题意相当有内涵了,不错,仿佛很公平,就这么办吧~不过,当剔选过程不断进行下去的时候,这个富人傻眼了,他发现前14个被剔除的孩子都是前妻生的,而且下一个要被剔除的还是前妻生的,富人马上大手一挥,停,现在从这个孩子倒回去数, 继室,就是这个歹毒的后妈一想,倒数就倒数,我15个儿子还斗不过你一个啊~她立即同意了富人的动议,你猜,到底谁做了继承人呢~
【52】“有一牧场,已知养牛27头,6天把草吃尽;养牛23头,9天把草吃尽。如果养牛21头,那么几天能把牧场上的草吃尽呢?并且牧场上的草是不断生长的。”
【53】一个商人骑一头驴要穿越1000公里长的沙漠,去卖3000根胡萝卜。已知驴一次性可驮1000根胡萝卜,但每走一公里又要吃掉一根胡萝卜。问:商人共可卖出多少胡萝卜?
【54】10箱黄金,每箱100块,每块一两
有贪官,把某一箱的每块都磨去一钱
请称一次找到不足量的那个箱子
【55】你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段,你必须在每天结束时都付费,如果只许你两次把金条弄断,你如何给你的工人付费?
【56】有十瓶药,每瓶里都装有100片药(仿佛现在装一百片的少了,都是十片二十片的,不管,咱们就这么来了),其中有八瓶里的药每片重10克,另有两瓶里的药每片重9克。用一个蛮精确的小秤,只称一次,如何找出份量较轻的那两个药瓶?
【57】一个经理有三个女儿,三个女儿的年龄加起来等于13,三个女儿的年龄乘起来等于经理自己的年龄,有一个下属已知道经理的年龄,但仍不能确定经理三个女儿的年龄,这时经理说只有一个女儿的头发是黑的,然后这个下属就知道了经理三个女儿的年龄。请问三个女儿的年龄分别是多少?为什么?
【58】有三个人去住旅馆,住三间房,每一间房$10元,于是他们一共付给老板$30,第二天,老板觉得三间房只需要$25元就够了于是叫小弟退回$5给三位客人,谁知小弟贪心,只退回每人$1,自己偷偷拿了$2,这样一来便等于那三位客人每人各花了九元,于是三个人一共花了$27,再加上小弟独吞了不$2,总共是$29。可是当初他们三个人一共付出$30那么还有$1呢?
【59】有两位盲人,他们都各自买了两对黑袜和两对白袜,八对袜了的布质、大小完全相同,而每对袜了都有一张商标纸连着。两位盲人不小心将八对袜了混在一起。他们每人怎样才能取回黑袜和白袜各两对呢?
【60】有一辆火车以每小时 15公里的速度离开洛杉矶直奔纽约,另一辆火车以每小时20公里的速度从纽约开往洛杉矶。如果有一只鸟,以30公里每小时的速度和两辆火车同时启动,从洛杉矶出发,碰到另一辆车后返回,依次在两辆火车来回飞行,直到两辆火车相遇,请问,这只小鸟飞行了多长距离?
【61】你有两个罐子,50个红色弹球,50个蓝色弹球,随机选出一个罐子,随机选取出一个弹球放入罐子,怎么给红色弹球最大的选中机会?在你的计划中,得到红球的准确几率是多少?
【62】你有四个装药丸的罐子,每个药丸都有一定的重量,被污染的药丸是没被污染的重量+1.只称量一次,如何判断哪个罐子的药被污染了?
【63】对一批编号为1~100,全部开关朝上(开)的灯进行以下*作:凡是1的倍数反方向拨一次开关;2的倍数反方向又拨一次开关;3的倍数反方向又拨一次开关……问:最后为关熄状态的灯的编号。
【64】想象你在镜子前,请问,为什么镜子中的影像可以颠倒左右,却不能颠倒上下?
【65】一群人开舞会,每人头上都戴着一顶帽子。帽子只有黑白两种,黑的至少有一顶。每个人都能看到其它人帽子的颜色,却看不到自己的。主持人先让大家看看别人头上戴的是什幺帽子,然后关灯,如果有人认为自己戴的是黑帽子,就打自己一个耳光。第一次关灯,没有声音。于是再开灯,大家再看一遍,关灯时仍然鸦雀无声。一直到第三次关灯,才有劈劈啪啪打耳光的声音响起。问有多少人戴着黑帽子?
【66】两个圆环,半径分别是1和2,小圆在大圆内部绕大圆圆周一周,问小圆自身转了几周?如果在大圆的外部,小圆自身转几周呢?
【67】 1元钱一瓶汽水,喝完后两个空瓶换一瓶汽水,问:你有20元钱,最多可以喝到几瓶汽水?
【68】有3顶红帽子,4顶黑帽子,5顶白帽子。让10个人从矮到高站成一队,给他们每个人头上戴一顶帽子。每个人都看不见自己戴的帽子的颜色,却只能看见站在前面那些人的帽子颜色。(所以最后一个人可以看见前面9个人头上帽子的颜色,而最前面那个人谁的帽子都看不见。现在从最后那个人开始,问他是不是知道自己戴的帽子颜色,如果他回答说不知道,就继续问他前面那个人。假设最前面那个人一定会知道自己戴的是黑帽子。为什么?
【69】假设排列着100个乒乓球,由两个人轮流拿球装入口袋,能拿到第100个乒乓球的人为胜利者。条件是:每次拿球者至少要拿1个,但最多不能超过5个,问:如果你是最先拿球的人,你该拿几个?以后怎么拿就能保证你能得到第100个乒乓球?
【70】卢姆教授说:“有一次我目击了两只山羊的一场殊死决斗,结果引出了一个有趣的数学问题。我的一位邻居有一只山羊,重54磅,它已有好几个季度在附近山区称王称霸。后来某个好事之徒引进了一只新的山羊,比它还要重出3磅。开始时,它们相安无事,彼此和谐相处。可是有一天,较轻的那只山羊站在陡峭的山路顶上,向它的竞争对手猛扑过去,那对手站在土丘上迎接挑战,而挑战者显然拥有居高临下的优势。不幸的是,由于猛烈碰撞,两只山羊都一命呜呼了。
现在要讲一讲本题的奇妙之处。对饲养山羊颇有研究,还写过书的乔治.阿伯克龙比说道:“通过反复实验,我发现,动量相当于一个自20英尺高处坠落下来的30磅重物的一次撞击,正好可以打碎山羊的脑壳,致它死命。”如果他说得不错,那么这两只山羊至少要有多大的逼近速度,才能相互撞破脑壳?你能算出来吗?
【71】据说有人给酒肆的老板娘出了一个难题:此人明明知道店里只有两个舀酒的勺子,分别能舀7两和11两酒,却硬要老板娘卖给他2两酒。聪明的老板娘毫不含糊,用这两个勺子在酒缸里舀酒,并倒来倒去,居然量出了2两酒,聪明的你能做到吗?
【72】已知: 每个飞机只有一个油箱, 飞机之间可以相互加油(注意是相互,没有加油机)一箱油可供一架飞机绕地球飞半圈,问题:为使至少一架飞机绕地球一圈回到起飞时的飞机场,至少需要出动几架飞机?(所有飞机从同一机场起飞,而且必须安全返回机场,不允许中途降落,中间没有飞机场)

【73】在9个点上画10条直线,要求每条直线上至少有三个点?

【74】一个岔路口分别通向诚实国和说谎国。来了两个人,已知一个是诚实国的,另一个是说谎国的。诚实国永远说实话,说谎国永远说谎话。现在你要去说谎国,但不知道应该走哪条路,需要问这两个人。请问应该怎么问?
【75】在一天的24小时之中,时钟的时针、分针和秒针完全重合在一起的时候有几次?都分别是什么时间?你怎样算出来的?

答案:

【1】

1、先把5升的灌满,倒在6升里,这时6升的壶里有5升水

2.再把5升的灌满,用5升的壶把6升的灌满,这时5升的壶里剩4升水

3.把6升的水倒掉,再把5升壶里剩余的水倒入6升的壶里,这时6升的壶里有4升水

4.把5升壶灌满,倒入6升的壶,5-2=3

【2】

把第二个满着的杯子里的水倒到第五个空着的杯子里

【3】

小黄。因为小李是第一个出手的,他要解决的第一个人就会是

小林,这样就会保证自己的安全,因为如果小黄被解决,自己理所当然地会成为小林的目标,他也必定会被打死。而小黄如果第一枪不打小林而去打小李,自己肯定会死(他命中较高,会成为接下来的神枪手小林的目标)。他必定去尝试先打死小林。那么30% 50%的几率是80%(第一回合小林的死亡率,但会有一点点偏差,毕竟相加了)。那么第一回合小黄的死亡率是20%多一点点(小林的命中减去自己的死亡率)。假设小林第一回合死了,就轮到小李打小黄了,那么小李的命中就变成了50%多一点点(自己的命中加上小黄的死亡率)。这样就变成了小李小黄对决,

第二回合的小李的第一枪命中是50%,小黄也是。可是如果拖下去的话占上风的自然就是小黄了,可能赢得也自然是小黄了。至于策略我看大家都领悟了吧。

【4】

甲分三碗汤,乙选认为最多和最少的倒回灌里再平分到剩余的两个碗里,让丁先选,其次是甲,最后是乙

【5】

假如先前N个中没有重叠且边上的都超出桌子的边上且全都是紧靠着的.那么根据题意就可以有:

空隙个数Y=3N/2 3(自己推算)

每一个空都要一个圆来盖

桌面就一共有圆的数为:

Y N=3N/2 3

=5N/2 3 <=4N(除N=1外)

所以可以用4N个硬币完全覆盖.

【6】

用绳子围球一周后测绳长来计算半径(用纸筒套住球来测更准)

借助排水法测体积后计算半径

【7】

要两人才能做到,

先在平面上摆放一枚,再在这枚硬币的正面立着放两枚(这两枚是侧面接触的),这样,这三枚硬币之间形成一个三角形空隙。剩下的两枚在空隙处交叉就行了,注意这两枚同样是平躺着,但可能需要翘起一定的角度。

【8】

方块5

从第一句话可以排除掉 黑桃J,8,2,7,3 草花K,6因为这种点数只出现一次.

从第二句话可以知道Q所知道的花色中所有的点数都出现过两次或以上才肯定P不知道是哪一张牌.这样我们可以看出只有红桃和方块存在这种现象,所以必然是这两种花色之一.

从第三句话P肯定自己知道是什么牌可以知道这个点数在红桃和方块里肯定是唯一性,所以可以排除红桃和方块A还剩下红桃Q ,4和方块5不能肯定了.但是Q知道花色啊.所以

从第四句话可以肯定就是方块5了,因为是红桃中的其中一个的话,Q是不能判断他知道的.

所以结论就是方块5

【9】

经过第一轮,说明任何两个数都是不同的。第二轮,前两个人没有猜出,说明任何一个数都不是其它数的两倍。现在有了以下几个条件:1.每个数大于02.两两不等3.任意一个数不是其他数的两倍。每个数字可能是另两个之和或之差,第三个人能猜出144,必然根据前面三个条件排除了其中的一种可能。假设:是两个数之差,即x-y=144。这时1(x,y>0)和2(x!=y)都满足,所以要否定x+y必然要使3不满足,即x+y=2y,解得x=y,不成立(不然第一轮就可猜出),所以不是两数之差。因此是两数之和,即x+y=144。同理,这时1,2都满足,必然要使3不满足,即x-y=2y,两方程联立,可得x=108,y=36。

这两轮猜的顺序其实分别为这样:第一轮(一号,二号),第二轮(三号,一号,二号)。这样分大家在每轮结束时获得的信息是相同的(即前面的三个条件)。

那么就假设我们是C,来看看C是怎么做出来的:C看到的是A的36和B的108,因为条件,两个数的和是第三个,那么自己要么是72要么是144(猜到这个是因为72的话,108就是36和72的和,144的话就是108和36的和。这样子这句话看不懂的举手):

假设自己(C)是72的话,那么B在第二回合的时候就可以看出来,下面是如果C是72,B的思路:这种情况下,B看到的就是A的36和C的72,那么他就可以猜自己,是36或者是108(猜到这个是因为36的话,36加36等于72,108的话就是36和108的和):

如果假设自己(B)头上是36,那么,C在第一回合的时候就可以看出来,下面是如果B是36,C的思路:这种情况下,C看到的就是A的36和B的36,那么他就可以猜自己,是72或者是0(这个不再解释了):

如果假设自己(C)头上是0,那么,A在第一回合的时候就可以看出来,下面是如果C是0,A的思路:这种情况下,A看到的就是B的36和C的0,那么他就可以猜自己,是36或者是36(这个不再解释了),那他可以一口报出自己头上的36。(然后是逆推逆推逆推),现在A在第一回合没报出自己的36,C(在B的想象中)就可以知道自己头上不是0,如果其他和B的想法一样(指B头上是36),那么C在第一回合就可以报出自己的72。现在C在第一回合没报出自己的36,B(在C的想象中)就可以知道自己头上不是36,如果其他和C的想法一样(指C头上是72),那么B在第二回合就可以报出自己的108。现在B在第二回合没报出自己的108,C就可以知道自己头上不是72,那么C头上的唯一可能就是144了。

【10】

15%80%/(85%×20%+15%80%)

【11】

f(x)=(60-2x)*x,当x=15时,有最大值450。

1820元设是X公里处赚最多钱。问题就成是求一个一元二次方程的最大值,求得是在15公里处赚钱最多,450元。一共240公斤……

【12】

6种结果

大、中、小:(2\30\68)(5\25\70)(8\20\72)(11\15\74)(14\10\76)(17\5\78)

【13】

因为1=5,所以5=1

【14】

本题可用递归算法,但时间复杂度为2的n次方,也可以用动态规划法,时间复杂度为n的平方,实现起来相对要简单得多,但最方便的就是直接运用公式:排队的种数=(2n)!/[n!(n 1)!]。

如果不考虑电影院能否找钱,那么一共有(2n)!/[n!n!]种排队方法(即从2n个人中取出n个人的组合数),对于每一种排队方法,如果他会导致电影院无法找钱,则称为不合格的,这种的排队方法有(2n)!/[(n-1)!(n 1)!](从2n个人中取出n-1个人的组合数)种,所以合格的排队种数就是(2n)!/[n!n!]- (2n)!/[(n-1)!(n 1)!] =(2n)!/[n!(n 1)!]。至于为什么不合格数是(2n)!/[(n-1)!(n 1)!],说起来太复杂,这里就不讲了。

【15】

2元

【16】

M=5 C得第二名

因为ABC三人得分共40分,三名得分都为正整数且不等,所以前三名得分最少为6分,40=58=410=220=120,不难得出项目数只能是5.即M=5.

A得分为22分,共5项,所以每项第一名得分只能是5,故A应得4个第一名一个第二名.22=5*4 2,第二名得2分,又B百米得第一,9=5 1 1 1 1 所以跳高中只有C得第二名

B的5项共9分,其中百米第一5分,其它4项全是1分,9=5 1=1 1 1.即B除百米第一外全是第三,跳高第二必定是C所得

【17】

房子 黄 蓝 红 绿 白

国籍 挪威 丹麦 英国 德国 瑞士

饮料 矿泉水 茶 牛奶 咖啡 啤酒

宠物 猫 马 鸟 鱼 狗

香烟 DUNHILL 混合烟 PALLMALL PRINCE BLUE MASTER

【18】

1 2 3 4 5

蓝房子 绿 黄 红 白

北京人 上海 香港 天津 成都

茅台酒 葡萄 矿泉水 茶 啤酒

豆腐 面条 牛肉 比萨 鸡

健牌 希尔顿 万宝路 555 红塔山

马 狗 蛇 猫 鱼

【19】

A家先打:55

B家如果打:TT的话.

C家随便他吃不吃..

A家都不跟.(反正B家跟C家哪家有吃55的话,都不跟.除非A家88可以出就跟)

如果刚才是B家吃的话,就B家出牌:你看.B家最多也出44然后C家吃他66.如果他是出两个99那地主也不跟!;如果B家出单的话.地主还有一个2可以压!(反正B家跟C家肯定是会打对子的!)

照刚才那样.A家牌下面应该剩:2 K Q J T 9 7777 66 3333

B家:大王 小王 2 A K QQ JJ 9 8 55

C家:22 AAA K Q J T 99 8 44

A家吃完88后.B家吃JJ(反正无论如何.都会打单的.)要是打单的话.A家就用2压.B家双王不可能会压吧.(即使压了也没事.)

A家用2压完后就打:K Q J T 9

B家如果用双王吃的话.那等他出牌的时候.马上用3333吃他.如果B家没吃的话.C家会吃:A K Q J T

然后A家可以用3333压下A K Q J T 如果B家用双王吃的话.那正合我意了哈.!A家反正只剩下7777 66了等他打什么..都用7777吃他.最后打66

【20】

先拿下第一楼的钻石,然后在每一楼把手中的钻石与那一楼的钻石相比较,如果那一楼的钻石比手中的钻石大的话那就把手中的钻石换成那一层的钻石。

(因为“只能拿一次”是在外文翻译过来的,所以是总共只能拿一次,还是每层只能拿一次?无法知道。但如果这个和“在稻田一直走,不能回头,请你捡出最大的一个稻穗”这样的题目一样的话,那么上面的就是正确答案!)

【21】

假设这四个人分别为甲(1分钟)乙(2分钟)丙(5分钟)丁(10分钟)

第一次去:甲和乙 (2分钟)

第一次回:甲(1分钟)

第二次去:丙和丁(10分钟)

第二次回:乙(2分钟)

第三次去:甲和乙(2分钟)

总计 :17分钟

【22】

1/3

(因为你知道一共有两个小孩 其中一个是女孩 而你已知的那个女孩并不知道是她第一个孩子还是第二个孩子所以它的概率是1/3

如果题目换成 已知第一个是女孩 那么第二个是女孩的概率就是1/2了)

【23】

主要是因为如果是方的、长方的或椭圆的,盖子很容易掉进地下道!但圆形的盖子嘛,就可以避免这种情况了。另外、圆形的盖子可以节省材料,增大洞口面积,井盖及井座的强度增加不易轧坏。

【24】

\1. 天平一边放7 2=9克砝码,另一边放9克盐。

\2. 天平一边放7克砝码和刚才得到的9克盐,另一边放16克盐。

\3. 天平一边放刚才得到的16克盐和再刚才得到的9克盐,另一边放25克盐。

【25】

把第一块芯片与其它逐一对比,看看其它芯片对第一块芯片给出的是好是坏,如果给出是好的过半,那么说明这是好芯片,完毕。如果给出的是坏的过半,说明第一块芯片是坏的,那么就要在那些在给出第一块芯片是坏的芯片中,重复上述步骤,直到找到好的芯片为止。

【26】

12个时可以找出那个是重还是轻,13个时只能找出是哪个球,轻重不知。

把球编为①②③④⑤⑥⑦⑧⑨⑩⑾⑿。(13个时编号为⒀)

第一次称:先把①②③④与⑤⑥⑦⑧放天平两边,

㈠如相等,说明特别球在剩下4个球中。

把①⑨与⑩⑾作第二次称量,

⒈如相等,说明⑿特别,把①与⑿作第三次称量即可判断是⑿是重还是轻

⒉如①⑨<⑩⑾说明要么是⑩⑾中有一个重的,要么⑨是轻的。

把⑩与⑾作第三次称量,如相等说明⑨轻,不等可找出谁是重球。

⒊如①⑨>⑩⑾说明要么是⑩⑾中有一个轻的,要么⑨是重的。

把⑩与⑾作第三次称量,如相等说明⑨重,不等可找出谁是轻球。

㈡如左边<右边,说明左边有轻的或右边有重的

把①②⑤与③④⑥做第二次称量

⒈如相等,说明⑦⑧中有一个重,把①与⑦作第三次称量即可判断是⑦与⑧中谁是重球

⒉如①②⑤<③④⑥说明要么是①②中有一个轻的,要么⑥是重的。

把①与②作第三次称量,如相等说明⑥重,不等可找出谁是轻球。

⒊如①②⑤>③④⑥说明要么是⑤是重的,要么③④中有一个是轻的。

把③与④作第三次称量,如相等说明⑤重,不等可找出谁是轻球。

㈢如左边>右边,参照㈡相反进行。

当13个球时,第㈠步以后如下进行。

把①⑨与⑩⑾作第二次称量,

⒈如相等,说明⑿⒀特别,把①与⑿作第三次称量即可判断是⑿还是⒀特别,但判断不了轻重了。

⒉不等的情况参见第㈠步的⒉⒊

【27】

首先求解原题。每道题的答错人数为(次序不重要):26,21,19,15,9

第3分布层:答错3道题的最多人数为:(26 21 19 15 9)/3=30

第2分布层:答错2道题的最多人数为:(21 19 15 9)/2=32

第1分布层:答错1道题的最多人数为:(19 15 9)/1=43

Max_3=Min(30, 32, 43)=30。因此答案为:100-30=70。

其实,因为26小于30,所以在求出第一分布层后,就可以判断答案为70了。

要让及格的人数最少,就要做到两点:

\1. 不及格的人答对的题目尽量多,这样就减少了及格的人需要答对的题目的数量,也就只需要更少的及格的人

\2. 每个及格的人答对的题目数尽量多,这样也能减少及格的人数

由1得每个人都至少做对两道题目

由2得要把剩余的210道题目分给其中的70人: 210/3 = 70,让这70人全部题目都做对,而其它30人只做对了两道题

也很容易给出一个具体的实现方案:

让70人答对全部五道题,11人仅答对第一、二道题,10人仅答对第二、三道题,5人答对第三、四道题,4人仅答对第四、五道题

显然稍有变动都会使及格的人数上升。所以最少及格人数就是70人!

【28】

十年可能包含2-3个闰年,3652或3653天。

1900年这个闰年就是28天,1898~1907这10年就是3651天,闰年如果是整百的倍数,如1800,1900,那么这个数必须是400的倍数才有29天,比如1900年2月有28天,2000年2月有29天。

【29】

下行是对上一行的解释 所以新的应该是3个1 2个2 1个1 :312211

【30】

一,一根绳子从两头烧,烧完就是半个小时。

二,一根要一头烧,一根从两头烧,两头烧完的时候(30分),将剩下的一根另一端点着,烧尽就是45分钟。再从两头点燃第三根,烧尽就是1时15分。

【31】

第一个瓶子拿出一片,第二个瓶子拿出四片,第三个拿出十六片,……第m个拿出n 1的m-1次方片。把所有这些药片放在一起称重量。

【32】

取出标着15便士的盒中的一个硬币,如果是银的说明这个盒是20便士的,如果是镍的说明这个盒是10便士的,再由每个盒的标签都是错误的可以推出其它两个盒里的东西。

【33】

最少10,最多130

见下表,表中蓝色部分服从2为底的指数函数规律,红色部分的数值均为其左边与左上角的两个数之和。

x

0 1 2 3 4 5 6 7 8 9

x个点最多能把直线分成多少部分

1 2 3 4 5 6 7 8 9 10

x条直线最多能把平面分成多少部分

1 2 4 7 11 16 22 29 37 46

x个平面最多能把空间分成多少

【34】

第一步:游到水池中心。

第二步:从水池中心游到距中心R/4处,并始终保持鼠、水池中心、猫在一直线上。

第三步:沿与中心相反方向的直线游3R/4就可以到达水池边,而猫沿圆周到达那里需要3.14R,所以捉不到老鼠。

【35】

表示为880,接下来,将一个大桶的水倒入小桶中,倒满,表示为853,(第2个大桶减3,小桶加3)则过程如下:

880——853:将3斤给第1个人,变为850(此时4人分别有水3-0-0-0)

850——823:将2斤给第2个人,变为803(此时4人分别有水3-2-0-0)

803——830——533——560——263——281:将1斤给第1个人,变为280(此时4人分别有水4-2-0-0)

280——253——703——730——433——460——163:将1斤给第3个人,变为063(此时4人分别有水4-2-1-0)

063——081:将1斤给第4个人,变为080(此时4人分别有水4-2-1-1)

080——053——350——323:将2斤给第2个人,将2个3斤分别给第3、4个人,(此时4人分别有水4-4-4-4)

【36】

7点x分:(7 x/60)/12=x/60 x=7*60=420/11=38.2

第一次是7点38分,第二次是8点44分

【37】

马3600 牛2800 羊1600

【38】

100

【39】

砝码将以与猴子相同的速度上升,因为它们质量相同,受力也相同

【40】

旋转看速度,金的密度大,质量相同,所以金球的实际体积较小,因为外半径相同,所以金球的内半径较大,所以金球的转动惯量大,在相同的外加力矩之下,金球的角加速度较小,所以转得慢。

【41】

分成10+13两堆, 然后翻转10的那堆

【42】

作图如下:

●●●●●●●●●C●●●●●●●●●●

● ●

● ●

● ●

A C B

● ● ●

● ● ●

● ● ●

● B ● A ●

● ● ●

●●●●●●●●●●●●●●●●●●●●

答题完毕.

【43】

温度,先开一盏,足够长时间后关了,开另一盏,进屋看,亮的为后来开的,摸起来热的为先开的,剩下的一盏也就确定了。

四盏的情况:设四个开关为ABCD,先开AB,足够长时间后关B开C,然后进屋,又热又亮为A,只热不亮为B,只亮不热为C,不亮不热为D。

【44】

1, 改变赋值号.比如 ,-,=

2, 注意质数.

3, 可能把画面颠倒过来.

4, 然后就可以去考虑更改其他数字更改了

247-217=30

【45】

如果轮到第四个海盗分配:100,0

轮到第三个:99,0,1

轮到第二个:98,0,1,0

轮到第一个:97,0,1,0,2,这就是第一个海盗的最佳方案。

【46】

第一个人选择17时最优的。它有先动优势。他确实有可能被逼死,后面的2、3、4号也想把1号逼死,但做不到(起码确定性逼死做不到)

可以看一下,如果第1个人选择21,他的信息时暴露给第2个人的,那么,1号就将自己暴露在一个非常不利的环境下,2-4号就会选择20,五号就会被迫在1-19中选择,则1、5号处死。所以1号不会这样做,会选择一个更小的数。

1号选择一个<20的数后,2号没有动力选择一个偏离很大的数(因为这个游戏偏离大会死),只会选择 1或-1,取决于那个死的概率小一些,再考虑这些的时候,又必须逆向考虑,1号必须考虑2-4号的选择,2号必须考虑3、4号的选择,… …只有5号没得选择,因为前面是只有连着的两个数(且表示为N,N 1),所以5号必死,他也非常明白这一点,会随机选择一个数,来决定整个游戏的命运,但决定不了他自己的命运。

下面决定的就是1号会选择一个什么数,他仍然不会选择一个太大或太小的数,因为那样仍然是自己处于不利的地位(2-4号肯定不会留情面的),100/6=16.7(为什么除以6?因为5号会随机选择一个数,对1号来说要尽可能的靠近中央,2-4好也是如此,而且正因为2-4号如此,1号才如此… …),最终必然是在16、17种选择的问题。

对16、17进行概率的计算之后,就得出了3个人选择17,第四个人选择16时,为均衡的状态,第4号虽然选择16不及前三个人选择17生存的机会大,但是若选择17则整个游戏的人必死(包括他自己)!第3号没有动力选择16,因为计算概率可知生存机会不如17。

所以选择为17、17、17、16、X(1-33随机),1-3号生存机会最大。

【47】

这堆桃子至少有3121只。

第一只猴子扔掉1个,拿走624个,余2496个;

第二只猴子扔掉1个,拿走499个,余1996个;

第三只猴子扔掉1个,拿走399个,余1596个;

第四只猴子扔掉1个,拿走319个,余1276个;

第五只猴子扔掉1个,拿走255个,余4堆,每堆255个。

如果不考虑正负,-4为一解

考虑到要5个猴子分,假设分n次。

则题目的解: 5^n-4

本题为5^5-4=3121.

设共a个桃,剩下b个桃,则b=(4/5)((4/5)((4/5)((4/5)((4/5)(a-1)-1)-1)-1)-1)-1),即b=(1024a-8404)/3125 ; a=3b 8 53*(b 4)/1024,而53跟1024不可约,则令b=1020可有最小解,得a=3121 ,设桃数x,得方程

4/5{4/5{4/5[4/5(x-1)-1]-1}-1}=5n

展开得

256x=3125n 2101

故x=(3125n 2101)/256=12n 8 53*(n 1)/256

因为53与256不可约,所以判断n=255有一解.x为整数,等于3121

【48】

这堆椰子最少有15621

第一个人给了猴子1个,藏了3124个,还剩12496个;

第二个人给了猴子1个,藏了2499个,还剩9996个;

第三个人给了猴子1个,藏了1999个,还剩7996个;

第四个人给了猴子1个,藏了1599个,还剩6396个;

第五个人给了猴子1个,藏了1279个,还剩5116个;

最后大家一起分成5份,每份1023个,多1个,给了猴子。

【49】

答案应该是9月1日。

1)首先分析这10组日期,经观察不难发现,只有6月7日和12月2日这两组日期的

日数是唯一的。由此可知,如果小强得知的N是7或者2,那么他必定知道了老师的

生日。

2)再分析“小明说:如果我不知道的话,小强肯定也不知道”,而该10组日期的

月数分别为3,6,9,12,而且都相应月的日期都有两组以上,所以小明得知M后

是不可能知道老师生日的。

3)进一步分析“小明说:如果我不知道的话,小强肯定也不知道”,结合第2步

结论,可知小强得知N后也绝不可能知道。

4)结合第3和第1步,可以推断:所有6月和12月的日期都不是老师的生日,因为

如果小明得知的M是6,而若小强的N==7,则小强就知道了老师的生日。(由第

1步已经推出),同理,如果小明的M==12,若小强的N==2,则小强同样可以知道老师的生日。即:M不等于6和9。现在只剩下“3月4日 3月5日 3月8日 9月1日

9月5日”五组日期。而小强知道了,所以N不等于5(有3月5日和9月5日),此时,

小强的N∈(1,4,8)注:此时N虽然有三种可能,但对于小强只要知道其中的

一种,就得出结论。所以有“小强说:本来我也不知道,但是现在我知道了”,

对于我们则还需要继续推理

至此,剩下的可能是“3月4日 3月8日 9月1日”

5)分析“小明说:哦,那我也知道了”,说明M==9,N==1,(N==5已经被排除,3月份的有两组)

【50】

如果我问另一个人死亡之门在哪里,他会怎么回答?

最终得到的回答肯定是指向自由之门的。

【51】

10 11 12 13 14 15 16 17 18 19 20 21 22 23= 198

198/ 30= 6余18.

小孩子站在18号位置即可.

【52】

1)27头牛6天所吃的牧草为:27×6=162

(这162包括牧场原有的草和6天新长的草。)

(2)23头牛9天所吃的牧草为:23×9=207

(这207包括牧场原有的草和9天新长的草。)

(3)1天新长的草为:(207-162)÷(9-6)=15

(4)牧场上原有的草为:27×6-15×6=72

(5)每天新长的草足够15头牛吃,21头牛减去15头,剩下6头吃原牧场的草:

72÷(21-15)=72÷6=12(天)

【53】

假设出沙漠时有1000根萝卜,那么在出沙漠之前一定不只1000根,那么至少要驮两次才会出沙漠,那样从出发地到沙漠边缘都会有往返的里程,那所走的路程将大于3000公里,故最后能卖出萝卜的数量一定是小于1000根的。

那么在走到某一个位置的时候萝卜的总数会恰好是1000根。

因为驴每次最多驮1000,那么为了最大的利用驴,第一次卸下的地点应该是使萝卜的数量为2000的地点。

因为一开始有3000萝卜,驴必须要驮三次,设驴走X公里第一次卸下萝卜

则:5X=1000(吃萝卜的数量,也等于所行走的公里数)

X=200,也就是说第一次只走200公里

验算:驴驮1000根走200公里时剩800根,卸下600根,返回出发地

前两次就囤积了1200根,第三次不用返回则剩800根,则总共是2000根萝卜了。

第二次驴只需要驮两次,设驴走Y公里第二次卸下萝卜

则:3Y=1000, Y=333.3

验算:驴驮1000根走333.3公里时剩667根,卸下334根,返回第一次卸萝卜地点

第二次在途中会吃掉334根萝卜,到第二次卸萝卜地点是加上卸下的334根,刚好是1000根。

而此时总共走了:200 333.3=533.3公里,而剩下的466.7公里只需要吃466根萝卜

所以可以卖萝卜的数量就是1000-466=534.

【54】

编号为1到100箱, 每箱取跟编号相同数目的黄金, 称量. 少多少钱,就是多少编号的箱子不足.

【55】

分为, 1,2,4 三段.

第一天, 1个环给工人

第二天, 2个环给工人, 拿回一个环

第三天, 1个环给工人

第四天, 4个环给工人, 拿回1个环,2个环

第五天, 一个环给工人

第六天, 2个环给工人,拿回1个环

第七天, 1个环给工人.

【56】

编号1至10, 1号取10片, 2号取20片,以此类推.

称量所有取出药片, 缺少多少, 就是哪两个瓶子分量较轻.

【57】

显然3个女儿的年龄都不为0,要不爸爸就为0岁了,因此女儿的年龄都大于等于1岁。这样可以得下面的情况:1111=11,12**10=20,139=27,148=32,157=35,{166=36},{229=36},238=48,247=56,256=60,337=63,346=72,355=75,445=80因为下属已知道经理的年龄,但仍不能确定经理三个女儿的年龄,说明经理是36岁(因为{166=36},{22*9=36}),所以3个女儿的年龄只有2种情况,经理又说只有一个女儿的头发是黑的,说明只有一个女儿是比较大的,其他的都比较小,头发还没有长成黑色的,所以3个女儿的年龄分别为2,2,9!

【58】

应该是三个人付了9*3=27,其中2付给了小弟,25付给了老板

【59】

把每双袜子的商标撕开,然后每人拿每双的一只

【60】

S1= (15 20)t

S2= 30t

得到S2= 6/7 S1. 小鸟飞行两地距离的6/7.

【61】

一个罐子放一个红球,另一个罐子放49个红球和50个蓝球,概率接近75%

【62】

1号罐取一个药片, 2号罐取两个药片,3号罐取3个药片, 4号罐取4个药片.

称量总重量, 比正常重量重几, 就是几号罐子被污染了.

【63】

1 4 9

【64】

因为镜子和你平行.

如果镜子与人不平行, 就可以颠倒上下.

实际上镜子并没有颠倒左右,而是颠倒前后

【65】

1,若是两个人,设A、B是黑帽子,第二次关灯就会有人打耳光。原因是A看到B第一次没打耳光,就知道B也一定看到了有带黑帽子的人,可A除了知道B带黑帽子外,其他人都是白帽子,就可推出他自己是带黑帽子的人!同理B也是这么想的,这样第二次熄灯会有两个耳光的声音。

2,如果是三个人,A,B,C. A第一次没打耳光,因为他看到B,C都是带黑帽子的;而且假设自己带的是白帽子,这样只有BC戴的是黑帽子;按照只有两个人带黑帽子的推论,第二次应该有人打耳光;可第二次却没有。。。于是他知道B和C一定看到了除BC之外的其他人带了黑帽子,于是他知道BC看到的那个人一定是他,所以第三次有三个人打了自己一个耳光!

【66】

把大圆剪断拉直。小圆绕大圆圆周一周,就变成从直线的一头滚至另一头。因为直线长就是大圆的周长,是小圆周长的2倍,所以小圆要滚动2圈。

但是现在小圆不是沿直线而是沿大圆滚动,小圆因此还同时作自转,当小圆沿大圆滚动1周回到原出发点时,小圆同时自转1周。当小圆在大圆内部滚动时自转的方向与滚动的转向相反,所以小圆自身转了1周。当小圆在大圆外部滚动时自转的方向与滚动的转向相同,所以小圆自身转了3周。

这一题非常有迷惑性,小圆在外部时其实是3圈,你可以拿个硬币试试可以把圆看成一根绳子,长绳是短绳的2倍长,假设长绳开始接口在最底下,短绳接口在长绳接口处,然后短绳开始顺时针绕,当短绳接口对着正左时,这时其实才绕了长绳的1/4,转了180 90度,所以绕一圈是2704=3603 。同理小圆在内部时是1圈。也可以套用下列公式: 两圆圆心距/转动者半径=转动者切另一圆时的自转数!!

【67】

40瓶,20 10 5 2 1 1=39, 这时还有一个空瓶子,先向店主借一个空瓶,换来一瓶汽水喝完后把空瓶还给店主。

【68】

一共3红4黑5白,第十个人不知道的话,可推出前9个人的所有可能情况:

红 黑 白

3 3 3

3 2 4

3 1 5

2 3 4

2 2 5

1 3 5

如果第九个人不知道的话,可推出前8个人的所有可能情况:

红 黑 白

1 2 5

1 3 4

2 1 5

2 2 4

2 3 3

3 1 4

3 2 3

由此类推可知,当推倒第六个人时,会发现他已经肯定知道他自己戴的是什么颜色的帽子了.

“有3顶黑帽子,2顶白帽子。让三个人从前到后站成一排,给他们每个人头上戴一顶帽子。每个人都看不见自己戴的帽子的颜色,却只能看见站在前面那些人的帽子颜色。(所以最后一个人可以看见前面两个人头上帽子的颜色,中间那个人看得见前面那个人的帽子颜色但看不见在他后面那个人的帽子颜色,而最前面那个人谁的帽子都看不见。现在从最后那个人开始,问他是不是知道自己戴的帽子颜色,如果他回答说不知道,就继续问他前面那个人。事实上他们三个戴的都是黑帽子,那么最前面那个人一定会知道自己戴的是黑帽子。为什么?”

答案是,最前面的那个人听见后面两个人都说了“不知道”,他假设自己戴的是白帽子,于是中间那个人就看见他戴的白帽子。那么中间那个人会作如下推理:“假设我戴了白帽子,那么最后那个人就会看见前面两顶白帽子,但总共只有两顶白帽子,他就应该明白他自己戴的是黑帽子,现在他说不知道,就说明我戴了白帽子这个假定是错的,所以我戴了黑帽子。”问题是中间那人也说不知道,所以最前面那个人知道自己戴白帽子的假定是错的,所以他推断出自己戴了黑帽子。

我们把这个问题推广成如下的形式:

“有若干种颜色的帽子,每种若干顶。假设有若干个人从前到后站成一排,给他们每个人头上戴一顶帽子。每个人都看不见自己戴的帽子的颜色,而且每个人都看得见在他前面所有人头上帽子的颜色,却看不见在他后面任何人头上帽子的颜色。现在从最后那个人开始,

问他是不是知道自己戴的帽子颜色,如果他回答说不知道,就继续问他前面那个人。一直往前问,那么一定有一个人知道自己所戴的帽子颜色。”

当然要假设一些条件:

1)首先,帽子的总数一定要大于人数,否则帽子都不够戴。

2)“有若干种颜色的帽子,每种若干顶,有若干人”这个信息是队列中所有人都事先知道的,而且所有人都知道所有人都知道此事,所有人都知道所有人都知道所有人都知道此事,等等等等。但在这个条件中的“若干”不一定非要具体一一给出数字来。

这个信息具体地可以是象上面经典的形式,列举出每种颜色帽子的数目“有3顶黑帽子,2顶白帽子,3个人”,也可以是“有红黄绿三种颜色的帽子各1顶2顶3顶,但具体不知道哪种颜色是几顶,有6个人”,甚至连具体人数也可以不知道,“有不知多少人排成一排,有黑白两种帽子,每种帽子的数目都比人数少1”,这时候那个排在最后的人并不知道自己排在最后——直到开始问他时发现在他回答前没有别人被问到,他才知道他在最后。在这个帖子接下去的部分当我出题的时候我将只写出“有若干种颜色的帽子,每种若干顶,有若干人”这个预设条件,因为这部分确定了,题目也就确定了。

3)剩下的没有戴在大家头上的帽子当然都被藏起来了,队伍里的人谁都不知道都剩下些什么帽子。

4)所有人都不是色盲,不但不是,而且只要两种颜色不同,他们就能分别出来。当然他们的视力也很好,能看到前方任意远的地方。他们极其聪明,逻辑推理是极好的。总而言之,只要理论上根据逻辑推导得出来,他们就一定推导得出来。相反地如果他们推不出自己头上帽子的颜色,任何人都不会试图去猜或者作弊偷看——不知为不知。

5)后面的人不能和前面的人说悄悄话或者打暗号。

当然,不是所有的预设条件都能给出一个合理的题目。比如有99顶黑帽子,99顶白帽子,2个人,无论怎么戴,都不可能有人知道自己头上帽子的颜色。另外,只要不是只有一种颜色的帽子,在只由一个人组成的队伍里,这个人也是不可能说出自己帽子的颜色的。

但是下面这几题是合理的题目:

1)3顶红帽子,4顶黑帽子,5顶白帽子,10个人。

2)3顶红帽子,4顶黑帽子,5顶白帽子,8个人。

3)n顶黑帽子,n-1顶白帽子,n个人(n>0)。

4)1顶颜色1的帽子,2顶颜色2的帽子,……,99顶颜色99的帽子,100顶颜色100的帽子,共5000个人。

5)有红黄绿三种颜色的帽子各1顶2顶3顶,但具体不知道哪种颜色是几顶,有6个人。

6)有不知多少人(至少两人)排成一排,有黑白两种帽子,每种帽子的数目都比人数少1。

大家可以先不看我下面的分析,试着做做这几题。

如果按照上面3顶黑帽2顶白帽时的推理方法去做,那么10个人就可以把我们累死,别说5000个人了。但是3)中的n是个抽象的数,考虑一下怎么解决这个问题,对解决一般的问题大有好处。

假设现在n个人都已经戴好了帽子,问排在最后的那一个人他头上的帽子是什么颜色,什么时候他会回答“知道”?很显然,只有在他看见前面n-1个人都戴着白帽时才可能,因为这时所有的n-1顶白帽都已用光,在他自己的脑袋上只能顶着黑帽子,只要前面有一顶黑帽子,那么他就无法排除自己头上是黑帽子的可能——即使他看见前面所有人都是黑帽,他还是有可能戴着第n顶黑帽。

现在假设最后那个人的回答是“不知道”,那么轮到问倒数第二人。根据最后面那位的回答,他能推断出什么呢?如果他看见的都是白帽,那么他立刻可以推断出自己戴的是黑帽——要是他也戴着白帽,那么最后那人应该看见一片白帽,问到他时他就该回答“知道”了。但是如果倒数第二人看见前面至少有一顶黑帽,他就无法作出判断——他有可能戴着白帽,但是他前面的那些黑帽使得最后那人无法回答“知道”;他自然也有可能戴着黑帽。

这样的推理可以继续下去,但是我们已经看出了苗头。最后那个人可以回答“知道”当且仅当他看见的全是白帽,所以他回答“不知道”当且仅当他至少看见了一顶黑帽。这就是所有帽子颜色问题的关键!

如果最后一个人回答“不知道”,那么他至少看见了一顶黑帽,所以如果倒数第二人看见的都是白帽,那么最后那个人看见的至少一顶黑帽在哪里呢?不会在别处,只能在倒数第二人自己的头上。这样的推理继续下去,对于队列中的每一个人来说就成了:

“在我后面的所有人都看见了至少一顶黑帽,否则的话他们就会按照相同的判断断定自己戴的是黑帽,所以如果我看见前面的人戴的全是白帽的话,我头上一定戴着我身后那个人看见的那顶黑帽。”

我们知道最前面的那个人什么帽子都看不见,就不用说看见黑帽了,所以如果他身后的所有人都回答说“不知道”,那么按照上面的推理,他可以确定自己戴的是黑帽,因为他身后的人必定看见了一顶黑帽——只能是第一个人他自己头上的那顶。事实上很明显,第一个说出自己头上是什么颜色帽子的那个人,就是从队首数起的第一个戴黑帽子的人,也就是那个从队尾数起第一个看见前面所有人都戴白帽子的人。

这样的推理也许让人觉得有点循环论证的味道,因为上面那段推理中包含了“如果别人也使用相同的推理”这样的意思,在逻辑上这样的自指式命题有点危险。但是其实这里没有循环论证,这是类似数学归纳法的推理,每个人的推理都建立在他后面那些人的推理上,而对于最后一个人来说,他的身后没有人,所以他的推理不依赖于其他人的推理就可以成立,是归纳中的第一个推理。稍微思考一下,我们就可以把上面的论证改得适合于任何多种颜色的推论:

“如果我们可以从假设断定某种颜色的帽子一定会在队列中出现,从队尾数起第一个看不见这种颜色的帽子的人就立刻可以根据和此论证相同的论证来作出判断,他戴的是这种颜色的帽子。现在所有我身后的人都回答不知道,所以我身后的人也看见了此种颜色的帽子。如果在我前面我见不到此颜色的帽子,那么一定是我戴着这种颜色的帽子。”

当然第一个人的初始推理相当简单:“队列中一定有人戴这种颜色的帽子,现在我看不见前面有人戴这颜色的帽子,那它只能是戴在我的头上了。”

对于题1)事情就变得很明显,3顶红帽子,4顶黑帽子,5顶白帽子给10个人戴,队列中每种颜色至少都该有一顶,于是从队尾数起第一个看不见某种颜色的帽子的人就能够断定他自己戴着这种颜色的帽子,通过这点我们也可以看到,最多问到从队首数起的第三人时,就应该有人回答“知道”了,因为从队首数起的第三人最多只能看见两顶帽子,所以最多看见两种颜色,如果他后面的人都回答“不知道”,那么他前面一定有两种颜色的帽子,而他头上戴的一定是他看不见的那种颜色的帽子。

题2)也一样,3顶红帽子,4顶黑帽子,5顶白帽子给8个人戴,那么队列中一定至少有一顶白帽子,因为其它颜色加起来一共才7顶,所以队列中一定会有人回答“知道”。

题4)的规模大了一点,但是道理和2)完全一样。100种颜色的5050顶帽子给5000人戴,前面99种颜色的帽子数量是1 …… 99=4950,所以队列中一定有第100种颜色的帽子(至少有50顶),所以如果自己身后的人都回答“不知道”,那么那个看不见颜色100帽子的人就可以断定自己戴着这种颜色的帽子。

至于5)、6)“有红黄绿三种颜色的帽子各1顶2顶3顶,但具体不知道哪种颜色是几顶,有6个人”以及“有不知多少人排成一排,有黑白两种帽子,每种帽子的数目都比人数少1”,原理完全相同,我就不具体分析了。

最后要指出的一点是,上面我们只是论证了,如果我们可以根据各种颜色帽子的数量和队列中的人数判断出在队列中至少有一顶某种颜色的帽子,那么一定有一人可以判断出自己头上的帽子的颜色。因为如果所有身后的人都回答“不知道”的话,那个从队尾数起第一个看不见这种颜色的帽子的人就可以判断自己戴了此颜色的帽子。但是这并不是说在询问中一定是由他来回答“知道”的,因为还可能有其他的方法来判断自己头上帽子的颜色。比如说在题2)中,如果队列如下:(箭头表示队列中人脸朝的方向)

白白黑黑黑黑红红红白→

那么在队尾第一人就立刻可以回答他头上的是白帽,因为他看见了所有的3顶红帽子和4顶黑帽子,能留给他自己戴的只能是白帽子了

【69】

拿出4个, 然后按照6的倍数和另外一人分别拿球. 即

另外一人拿1个, 我拿5个

另外一人拿2个, 我拿4个

另外一人拿3个, 我拿3个

另外一人拿4个, 我拿2个

另外一人拿5个, 我拿1个.

最终100个在我手上.

首先拿4个 别人拿n个你就拿6-n个

【70】

1英尺(ft)=0.3048米(m)

1磅(lb)=0.454千克(kg)

通过实验得到撞破脑壳所需要的机械能是mgh=(300.454)9.8(200.3048)=813.669(J)对于两只山羊撞击瞬间来说,比较重的那只仅仅是站在原地,只有较轻的山羊具有速度,而题目中暗示我们,两只羊仅一次碰撞致死。现在我们只需要求得碰撞瞬间轻山羊的瞬时速度就可以了,根据机械能守恒定律:mgh=1/2(m1v^2)可以得出速度。m1是轻山羊的重量。

【71】

7两倒入11两, 再用7两倒入11两装满, 7两中剩余3两, 倒出11两, 将3两倒入11两, 用7两两次倒入11两装满, 7两中剩余6两, 将11两倒出, 将6两倒入, 然后用7两倒入11两, 剩余2两. 于是得到.

11,0–>4,7–>4,0–>0,4–>11,4–>8,7–>8,0–>1,7–>1,0–>0,1–>11,1–>5,7–>5,0–>0,5–>11,5–>9,7–>9,0–>2,7

【72】

需要4飞机.

假设需要三架飞机,编号为1,2,3.

三架同时起飞, 飞到1/8 圈处, 1号飞机,给2号,3号,飞机各加上1/8 圈的油, 刚好飞回基地,此时1号,2号满油,继续前飞;

飞到2/8 圈时候,2号飞机给1号飞机加油1/8圈油量,刚好飞回基地, 3号飞机满油,继续向前飞行, 到达6/8处无油;

此时重复2号和三号飞机的送油.3号飞机反方向飞行到1/6圈时, 加油1/6圈给给2号飞机, 2号飞机向前飞行X圈, 则3号飞机可向前继续送油, 1/6 –2X 圈. 此时3号刚好飞回, 2号满油.当X= 1/6-2X时候获得最大. X =1/18.

1/6 1/18= 2/ 9. 少于1/4. 所以不能完成.

类比推,当为4架时, 恰好满足条件.

【73】

排列如下所示.X代表点, O代表空格.

X O X

O X O

X X X

O X O

X O X

得到10条.

【74】

我要到你的国家去,请问怎么走?然后走向路人所指方向的相反方向.

【75】

只有两次

假设时针的角速度是ω(ω=π/6每小时),则分针的角速度为12ω,秒针的角速度为72ω。分针与时针再次重合的时间为t,则有12ωt-ωt=2π,t=12/11小时,换算成时分秒为1小时5分27.3秒,显然秒针不与时针分针重合,同样可以算出其它10次分针与时针重合时秒针都不能与它们重合。只有在正12点和0点时才会重。

证明:将时针视为静止,考察分针,秒针对它的相对速度:

12个小时作为时间单位“1”,“圈/12小时”作为速度单位,

则分针速度为11,秒针速度为719。

由于11与719互质,记12小时/(11*719)为时间单位Δ,

则分针与时针重合当且仅当 t=719kΔ k∈Z

秒针与时针重合当且仅当 t=11jΔ j∈Z

而719与11的最小公倍数为11*719,所以若t=0时三针重合,则下一次三针重合

必然在t=11719Δ时,即t=12点。

关于JS的继承

发表于 2017-03-08

关于JS的继承

众所周知,在 ES 6 之前没有类的概念,所以不能像 Java 中一个 extends 关键字就搞定了继承关系,需要一些 tricks 来实现,下面就收集一些比较常用的方法。

(一) 原型链继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name) {
this.name = name;
}
Child.prototype = new Parent('father');
Child.prototype.constructor = Child;
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
var child = new Child('son');
child.sayName(); // child name: son

只要是原型链中出现过的原型,都可以说是该原型链派生的实例的原型。

这种方法存在两个缺点:

  1. 子类型无法给超类型传递参数,在面向对象的继承中,我们总希望通过 var child = new Child(‘son’, ‘father’); 让子类去调用父类的构造器来完成继承。而不是通过像这样 new Parent(‘father’) 去调用父类。
  2. Child.prototype.sayName 必须写在 Child.prototype = new Parent(‘father’); 之后,不然就会被覆盖掉。(在node-v7.6.0下运行无任何问题)

(二) 类式继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
Parent.prototype.doSomthing = function() {
console.log('parent do something!');
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
var child = new Child('son');
child.sayName(); // child name: son
child.doSomthing(); // TypeError: child.doSomthing is not a function

相当于 Parent 这个函数在 Child 函数中执行了一遍,并且将所有与 this 绑定的变量都切换到了 Child 上,这样就克服了第一种方式带来的问题。

缺点:

  1. 没有原型,每次创建一个 Child 实例对象时候都需要执行一遍 Parent 函数,无法复用一些公用函数。

(三) 组合式继承:前两种方式的结合

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
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
Parent.prototype.doSomething = function() {
console.log('parent do something!');
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype = new Parent();
Child.prototype.construtor = Child;
var child = new Child('son');
child.sayName(); // child name: son
child.doSomething(); // parent do something!

组合式继承是比较常用的一种继承方法,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。

组合式继承是 JS 最常用的继承模式,但组合继承使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
Parent.prototype.doSomething = function() {
console.log('parent do something!');
}
function Child(name, parentName) {
Parent.call(this, parentName); // 第二次调用
this.name = name;
}
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype = new Parent(); // 第一次调用
Child.prototype.construtor = Child;
var child = new Child('son');
child.sayName();
child.doSomething();

显然从上述的代码中可以看出,第一次调用构造函数显然是没有必要的,因为第一次调用构造函数时候不需要函数内部的那些实例属性,这么写只是想获得其原型上的方法罢了,所以这时候你可能会这样写:

1
Child.prototype = Parent.prototype;

这样写显然是不对的:

  1. 首先,你这样写的话相当于是子类和父类都指向同一个对象,这时候如果你添加了新的方法给 Child 但实际上 Parent 并不需要,相当于强行给 Parent 添加了一个未知的方法。
  2. 其次,仔细想想,这样体现不出继承的多态性,比如此时子类想要重写父类的 getName 的方法,那么父类的方法也就会随之修改,这显然违背了多态性。

也就是说我们第一次调用构造函数的时候,其实是不管构造函数里面的内容,所以我们何不 new 一个空函数,将其 prototype 指向 Parent.prototype,代码如下:

(四) 寄生组合式继承:

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
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function create(proto) {
function F(){}
F.prototype = proto;
F.prototype.construtor = F;
return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype.construtor = Child;
var parent = new Parent('father');
parent.sayName(); // parent name: father
var child = new Child('son', 'father');
child.sayName(); // child name: son

这就是所谓的寄生组合式继承方式,跟组合式继承的区别在于,他不需要在一次实例中调用两次父类的构造函数,假如说父类的构造器代码很多,还需要调用两次的话对系统肯定会有影响,寄生组合式继承的思想在于:用一个 F 空的构造函数去取代执行了 Parent 这个构造函数。

在上面的代码中,我们手动创建了一个 create 函数,但是其实是存在于 Object 对象中,不需要我们手动去创建,所以上面的代码可以改为:

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
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function inheritPrototype(Parent, Child) {
Child.prototype = Object.create(Parent.prototype); //修改
Child.prototype.construtor = Child;
}
inheritPrototype(Parent, Child);
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
var parent = new Parent('father');
parent.sayName(); // parent name: father
var child = new Child('son', 'father');
child.sayName(); // child name: son

(五) ES 6 继承:

当然,如果你学习过 ES 6,那么写继承关系就会特别简单,如果你学过 Java 就会发现,ES 6 中的继承跟 Java 太像了,上述的代码可改为:

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
class Parent {
constructor(name) {
this.name = name;
}
doSomething() {
console.log('parent do something!');
}
sayName() {
console.log('parent name:', this.name);
}
}
class Child extends Parent {
constructor(name, parentName) {
super(parentName);
this.name = name;
}
sayName() {
console.log('child name:', this.name);
}
}
const child = new Child('son', 'father');
child.sayName(); // child name: son
child.doSomething(); // parent do something!
const parent = new Parent('father');
parent.sayName(); // parent name: father

本文来源于网络:30 分钟学会 JS 继承 - 掘金

基于类vs基于原型的语言

发表于 2017-03-06

基于类 vs 基于原型的语言

JavaScript 是一种基于原型的面向对象语言,而不是基于类的。正是由于这一根本的区别,其如何创建对象的层级结构以及对象的属性与属性值是如何继承的并不是那么清晰。本节试着阐明。

基于类的面向对象语言,比如 Java 和 C++,是构建在两个不同实体的概念之上的:即类和实例。

  • 类(class):定义了所有用于具有某一组特征对象的属性(可以将 Java 中的方法和变量以及 C++ 中的成员都视作属性)。类是抽象的事物,而不是其所描述的全部对象中的任何特定的个体。例如 Employee 类可以用来表示所有雇员的集合。
  • 实例(instance):类的实例化体现;或者说,是类的一个成员。例如, Victoria 可以是 Employee 类的一个实例,表示一个特定的雇员个体。实例具有和其父类完全一致的属性。

基于原型的语言(如 JavaScript)并不存在这种区别:它只有对象。基于原型的语言具有所谓原型对象(prototypical object)的概念。原型对象可以作为一个模板,新对象可以从中获得原始的属性。任何对象都可以指定其自身的属性,既可以是创建时也可以在运行时创建。而且,任何对象都可以作为另一个对象的原型(prototype),从而允许后者共享前者的属性。

定义类

在基于类的语言中,需要专门的类定义符(class definition)定义类。在定义类时,允许定义特殊的方法,称为构造器(constructor),来创建该类的实例。在构造器方法中,可以指定实例的属性的初始值以及一些其他的操作。您可以通过将new 操作符和构造器方法结合来创建类的实例。

JavaScript 也遵循类似的模型,但却不同于基于类的语言。在 JavaScript 中你只需要定义构造函数来创建具有一组特定的初始属性和属性值的对象。任何 JavaScript 函数都可以用作构造器。 也可以使用 new 操作符和构造函数来创建一个新对象。

子类和继承

基于类的语言是通过对类的定义中构建类的层级结构的。在类定义中,可以指定新的类是一个现存的类的子类。子类将继承父类的全部属性,并可以添加新的属性或者修改继承的属性。例如,假设 Employee 类只有 name 和 dept 属性,而 Manager 是 Employee 的子类并添加了 reports 属性。这时,Manager 类的实例将具有所有三个属性:name,``dept 和 reports。

JavaScript 通过将构造器函数与原型对象相关联的方式来实现继承。这样,您可以创建完全一样的 Employee — Manager 示例,不过需要使用略微不同的术语。首先,定义 Employee 构造器函数,指定 name 和 dept 属性;然后,定义 Manager 构造器函数,指定 reports 属性。最后,将一个新的 Employee 对象赋值给 Manager 构造器函数的 prototype 属性。这样,当创建一个新的 Manager 对象时,它将从 Employee 对象中继承 name and dept 属性。

添加和移除属性

在基于类的语言中,通常在编译时创建类,然后在编译时或者运行时对类的实例进行实例化。一旦定义了类,无法对类的属性进行更改。然而,在 JavaScript 中,允许运行时添加或者移除任何对象的属性。如果您为一个对象中添加了一个属性,而这个对象又作为其它对象的原型,则以该对象作为原型的所有其它对象也将获得该属性。

区别摘要

下面的表格摘要给出了上述区别。本节的后续部分将描述有关使用 JavaScript 构造器和原型创建对象层级结构的详细信息,并将其与在 Java 中的做法加以对比。

基于类的(Java) 基于原型的(JavaScript)
类和实例是不同的事物。 所有对象均为实例。
通过类定义来定义类;通过构造器方法来实例化类。 通过构造器函数来定义和创建一组对象。
通过 new 操作符创建单个对象。 相同。
通过类定义来定义现存类的子类,从而构建对象的层级结构。 指定一个对象作为原型并且与构造函数一起构建对象的层级结构
遵循类链继承属性。 遵循原型链继承属性。
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。 构造器函数或原型指定初始的属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。

Employee 示例

本节的余下部分将使用如下图所示的 Employee 层级结构。

img

图8.1:一个简单的对象层级

例子中会使用以下对象:

  • Employee 具有 name 属性(默认值为空的字符串)和 dept 属性(默认值为 “general”)。
  • Manager 是 Employee的子类。它添加了 reports 属性(默认值为空的数组,以 Employee 对象数组作为它的值)。
  • WorkerBee 是 Employee的子类。它添加了 projects 属性(默认值为空的数组,以字符串数组作为它的值)。
  • SalesPerson 是 WorkerBee的子类。它添加了 quota 属性(其值默认为 100)。它还重载了 dept 属性值为 “sales”,表明所有的销售人员都属于同一部门。
  • Engineer 基于 WorkerBee。它添加了 machine 属性(其值默认为空的字符串)同时重载了 dept 属性值为 “engineering”。

创建层级结构

可以有几种不同的方式来定义适当的构造器函数,从而实现雇员的层级结构。如何选择很大程度上取决于您希望在您的应用程序中能做到什么。

本节介绍了如何使用非常简单的(同时也是相当不灵活的)定义,使得继承得以实现。在定义完成后,就无法在创建对象时指定属性的值。新创建的对象仅仅获得了默认值,当然允许随后加以修改。图例 8.2 展现了这些简单的定义形成的层级结构。

在实际应用程序中,您很可能想定义构造器,以允许您在创建对象时指定属性值。(参见 更灵活的构造器 获得进一步的信息)。当前,这些简单的定义只是说明了继承是如何实现的。

figure8.2.png
图 8.2:Employee 对象定义

下面关于 Employee 的 Java 和 JavaScript 的定义是非常类似的。唯一的不同是在 Java 中需要指定每个属性的类型,而在 JavaScript 中则不需要,同时 Java 的类必须创建一个显式的构造器方法。

JavaScript Java
function Employee () { this.name = ""; this.dept = "general";} public class Employee { public String name; public String dept; public Employee () { this.name = ""; this.dept = "general"; }}

Manager 和 WorkerBee 的定义表示在如何指定继承链中上一层对象时,两者存在不同点。在 JavaScript 中,您会添加一个原型实例作为构造器函数prototype 属性的值,而这一动作可以在构造器函数定义后的任意时刻执行。而在 Java 中,则需要在类定义中指定父类,且不能在类定义之外改变父类。

JavaScript Java
function Manager () { this.reports = [];}Manager.prototype = new Employee;function WorkerBee () { this.projects = [];}WorkerBee.prototype = new Employee; public class Manager extends Employee { public Employee[] reports; public Manager () { this.reports = new Employee[0]; }}public class WorkerBee extends Employee { public String[] projects; public WorkerBee () { this.projects = new String[0]; }}

在对Engineer 和 SalesPerson 定义时,创建了继承自 WorkerBee 的对象,该对象会进而继承自Employee。这些对象会具有在这个链之上的所有对象的属性。另外,它们在定义时,又重载了继承的 dept 属性值,赋予新的属性值。

JavaScript Java
function SalesPerson () { this.dept = "sales"; this.quota = 100;}SalesPerson.prototype = new WorkerBee;function Engineer () { this.dept = "engineering"; this.machine = "";}Engineer.prototype = new WorkerBee; public class SalesPerson extends WorkerBee { public double quota; public SalesPerson () { this.dept = "sales"; this.quota = 100.0; }}public class Engineer extends WorkerBee { public String machine; public Engineer () { this.dept = "engineering"; this.machine = ""; }}

在上述对象的定义完成后,您就可以创建这些对象的实例并获取其属性的默认值。图8.3 表示了使用JavaScript 创建新对象的过程并显示了新对象中的属性值。

Note: 术语 实例(instance)在基于类的语言中具有特定的技术含义。在这些语言中,实例是指类的个体成员,与类有着根本性的不同。在 JavaScript 中,“实例”并不具有这种技术含义,因为 JavaScript 中不存在类和实例之间的这种差异。然而,在谈论 JavaScript 时,“实例”可以非正式地用于表示用特定的构造器函数创建的对象。所以,在这个例子中,你可以非正式地称jane 是 Engineer 的一个实例。与之类似,尽管术语父(parent),子(child),祖先(ancestor),和后代(descendant)在 JavaScript 中并没有正式的含义,您可以非正式地使用这些术语用于指代原型链中处于更高层次或者更低层次的对象。

figure8.3.png
图例 8.3:通过简单的定义创建对象

对象的属性

本节将讨论对象如何从原型链中的其它对象中继承属性,以及在运行时添加属性的相关细节。

继承属性

假设您通过如下语句创建一个 mark 对象作为 WorkerBee 的实例(如 图例 8.3 所示):

1
var mark = new WorkerBee;

当 JavaScript 发现 new 操作符时,它会创建一个通用(generic)对象,并将其作为关键字 this 的值传递给 WorkerBee 的构造器函数。该构造器函数显式地设置 projects 属性的值,然后隐式地将其内部的 __proto__ 属性设置为 WorkerBee.prototype 的值(属性的名称前后均有两个下划线)。__proto__ 属性决定了用于返回属性值的原型链。一旦这些属性设置完成,JavaScript 返回新创建的对象,然后赋值语句会将变量 mark 的值指向该对象。

这个过程不会显式的将 mark所继承的原型链中的属性值作为本地变量存放在 mark 对象中。当请求属性的值时,JavaScript 将首先检查对象自身中是否存在属性的值,如果有,则返回该值。如果不存在,JavaScript会通过 __proto__对原型链进行检查。如果原型链中的某个对象包含该属性的值,则返回这个值。如果没有找到该属性,JavaScript 则认为对象中不存在该属性。这样,mark 对象中将具有如下的属性和对应的值:

1
2
3
mark.name = "";
mark.dept = "general";
mark.projects = [];

mark 对象从 mark.__proto__ 中保存的原型对象中继承了 name 和 dept 属性的值。并由 WorkerBee 构造器函数为 projects 属性设置了本地值。 这就是 JavaScript 中的属性和属性值的继承。这个过程的一些微妙之处将在 再谈属性继承 中进一步讨论。

由于这些构造器不支持为实例设置特定的值,所以这些属性值仅仅是创建自 WorkerBee 的所有对象所共享的默认值。当然这些属性的值是可以修改的,所以您可以为 mark指定特定的信息,如下所示:

1
2
3
mark.name = "Doe, Mark";
mark.dept = "admin";
mark.projects = ["navigator"];

添加属性

在 JavaScript 中,您可以在运行时为任何对象添加属性,而不必受限于构造器函数提供的属性。添加特定于某个对象的属性,只需要为该对象指定一个属性值,如下所示:

1
mark.bonus = 3000;

这样 mark 对象就有了 bonus 属性,而其它 WorkerBee 则没有该属性。

如果您向某个构造器函数的原型对象中添加新的属性,那么该属性将添加到从这个原型中继承属性的所有对象的中。例如,可以通过如下的语句向所有雇员中添加 specialty 属性:

1
Employee.prototype.specialty = "none";

只要 JavaScript 执行了该语句,则 mark 对象也将具有 specialty 属性,其值为 "none"。下图则表示了在 Employee 原型中添加该属性,然后在 Engineer的原型中重载该属性的效果。

img
图 8.4: 添加属性

更灵活的构造器

截至到现在,构造器函数都不允许在创建新的实例时指定属性值。其实我们也可以像Java一样,为构造器提供参数以初始化实例的属性值。下图即实现方式之一。

img
Figure 8.5: Specifying properties in a constructor, take 1

下面的表格中罗列了这些对象在 Java 和 JavaScript 中的定义。

JavaScript Java
`function Employee (name, dept) { this.name = name “”; this.dept = dept “general”;}` public class Employee { public String name; public String dept; public Employee () { this("", "general"); } public Employee (String name) { this(name, "general"); } public Employee (String name, String dept) { this.name = name; this.dept = dept; }}
`function WorkerBee (projs) { this.projects = projs [];}WorkerBee.prototype = new Employee;` public class WorkerBee extends Employee { public String[] projects; public WorkerBee () { this(new String[0]); } public WorkerBee (String[] projs) { projects = projs; }}
`function Engineer (mach) { this.dept = “engineering”; this.machine = mach “”;}Engineer.prototype = new WorkerBee;` public class Engineer extends WorkerBee { public String machine; public Engineer () { dept = "engineering"; machine = ""; } public Engineer (String mach) { dept = "engineering"; machine = mach; }}

上面使用 JavaScript 定义过程使用了一种设置默认值的特殊惯用法:

1
this.name = name || "";

JavaScript 的逻辑或操作符(||)会对第一个参数进行判断。如果该参数值运算后结果为真,则操作符返回该值。否则,操作符返回第二个参数的值。因此,这行代码首先检查 name 是否是对name 属性有效的值。如果是,则设置其为 this.name 的值。否则,设置 this.name 的值为空的字符串。尽管这种用法乍看起来有些费解,为了简洁起见,本章将使用这种习惯用法。

注意:如果调用构造器函数时,指定了可以转换为 false 的参数(比如 0 (零)和空字符串("")),结果可能出乎调用者意料。此时,将使用默认值(译者注:而不是指定的参数值 0 和 "")。

由上面的定义,当创建对象的实例时,您可以为本地定义的属性指定值。正如 图例 8.5 所示一样,您可以通过如下语句创建新的 Engineer:

1
var jane = new Engineer("belau");

此时,Jane 的属性如下所示:

1
2
3
4
jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

注意,由上面对类的定义,您无法为诸如 name 这样的继承属性指定初始值。如果想在JavaScript中为继承的属性指定初始值,您需要在构造器函数中添加更多的代码。

到目前为止,构造器函数已经能够创建一个普通对象,然后为新对象指定本地的属性和属性值。您还可以通过直接调用原型链上的更高层次对象的构造器函数,让构造器添加更多的属性。下图即实现了这一功能。

img
图 8.6 Specifying properties in a constructor, take 2

下面是 Engineer 构造器的定义:

1
2
3
4
5
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}

假设您创建了一个新的 Engineer 对象,如下所示:

1
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");

执行时,JavaScript 会有以下步骤:

  1. new 操作符创建了一个新的通用对象,并将其 __proto__ 属性设置为 Engineer.prototype。
  2. new 操作符将该新对象作为 this 的值传递给 Engineer 构造器。
  3. 构造器为该新对象创建了一个名为 base 的新属性,并指向 WorkerBee 的构造器。这使得 WorkerBee 构造器成为 Engineer 对象的一个方法。base 属性的名称并没有什么特殊性,我们可以使用任何其他合法的名称来代替;base 仅仅是为了贴近它的用意。
  4. 构造器调用 base 方法,将传递给该构造器的参数中的两个,作为参数传递给 base 方法,同时还传递一个字符串参数 "engineering"。显式地在构造器中使用 "engineering" 表明所有 Engineer 对象继承的 dept 属性具有相同的值,且该值重载了继承自 Employee 的值。
  5. 因为 base 是 Engineer 的一个方法,在调用 base 时,JavaScript 将在步骤 1 中创建的对象绑定给 this 关键字。这样,WorkerBee 函数接着将 "Doe, Jane" 和 "engineering" 参数传递给 Employee 构造器函数。当从 Employee 构造器函数返回时,WorkerBee 函数用剩下的参数设置 projects 属性。
  6. 当从 base 方法返回后,Engineer 构造器将对象的 machine 属性初始化为 "belau"。
  7. 当从构造器返回时,JavaScript 将新对象赋值给 jane 变量。

您可以认为,在 Engineer 的构造器中调用了 WorkerBee 的构造器,也就为 Engineer 对象设置好了继承关系。事实并非如此。调用 WorkerBee 构造器确保了Engineer 对象以所有在构造器中所指定的属性被调用。但是,如果后续在 Employee 或者 WorkerBee 原型中添加了属性,那些属性不会被 Engineer 对象继承。例如,假设如下语句:

1
2
3
4
5
6
7
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

对象 jane 不会继承 specialty 属性。您必须显式地设置原型才能确保动态的继承。如果修改成如下的语句:

1
2
3
4
5
6
7
8
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

现在 jane 对象的 specialty 属性为 “none” 了。

继承的另一种途径是使用call() / apply() 方法。下面的方式都是等价的:

`function Engineer (name, projs, mach) { this.base = WorkerBee; this.base(name, “engineering”, projs); this.machine = mach “”;}` `function Engineer (name, projs, mach) { WorkerBee.call(this, name, “engineering”, projs); this.machine = mach “”;}`

使用 javascript 的 call() 方法相对明了一些,因为无需 base 方法了。

再谈属性的继承

前面的小节中描述了 JavaScript 构造器和原型如何提供层级结构和继承的实现。本节中对之前未讨论的一些细节进行阐述。

本地值和继承值

正如本章前面所述,在访问一个对象的属性时,JavaScript 将执行下面的步骤:

  1. 检查本地值是否存在。如果存在,返回该值。
  2. 如果本地值不存在,检查原型链(通过 __proto__ 属性)。
  3. 如果原型链中的某个对象具有指定属性的值,则返回该值。
  4. 如果这样的属性不存在,则对象没有该属性。

以上步骤的结果依赖于您是如何定义的。最早的例子中具有如下定义:

1
2
3
4
5
6
7
8
9
function Employee () {
this.name = "";
this.dept = "general";
}
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;

基于这些定义,假定通过如下的语句创建 WorkerBee 的实例 amy:

1
var amy = new WorkerBee;

则 amy 对象将具有一个本地属性,projects。``name 和 dept 属性则不是 amy 对象本地的,而是从 amy 对象的 __proto__ 属性获得的。因此,amy 将具有如下的属性值:

1
2
3
amy.name == "";
amy.dept == "general";
amy.projects == [];

现在,假设修改了与 Employee 的相关联原型中的 name 属性的值:

1
Employee.prototype.name = "Unknown"

乍一看,您可能觉得新的值会传播给所有 Employee 的实例。然而,并非如此。

在创建 Employee 对象的任意实例时,该实例的 name 属性将获得一个本地值(空的字符串)。这就意味着在创建一个新的 Employee 对象作为 WorkerBee 的原型时,WorkerBee.prototype 的 name 属性将具有一个本地值。因此,当 JavaScript 查找 amy 对象(WorkerBee 的实例)的 name 属性时,JavaScript 将找到 WorkerBee.prototype 中的本地值。因此,也就不会继续在原型链中向上找到 Employee.prototype 了。

如果想在运行时修改一个对象的属性值并且希望该值被所有该对象的后代所继承,您就不能在该对象的构造器函数中定义该属性。而应该将该属性添加到该对象所关联的原型中。例如,假设将前面的代码作如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Employee () {
this.dept = "general";
}
Employee.prototype.name = "";
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
var amy = new WorkerBee;
Employee.prototype.name = "Unknown";

在这种情况下,amy 的 name 属性将为 “Unknown”。

正如这些例子所示,如果希望对象的属性具有默认值,并且希望在运行时修改这些默认值,应该在对象的原型中设置这些属性,而不是在构造器函数中。

判断实例的关系

JavaScript 的属性查找机制首先在对象自身的属性中查找,如果指定的属性名称没有找到,将在对象的特殊属性 __proto__ 中查找。这个过程是递归的;被称为“在原型链中查找”。

特殊的 __proto__ 属性是在构建对象时设置的;设置为构造器的 prototype 属性的值。所以表达式 new Foo() 将创建一个对象,其 __proto__ ==Foo.prototype。因而,修改 Foo.prototype 的属性,将改变所有通过 new Foo() 创建的对象的属性的查找。

每个对象都有一个 __proto__ 对象属性(除了 Object);每个函数都有一个 prototype 对象属性。因此,通过“原型继承(prototype inheritance)”,对象与其它对象之间形成关系。通过比较对象的 __proto__ 属性和函数的 prototype 属性可以检测对象的继承关系。JavaScript 提供了便捷方法:instanceof 操作符可以用来将一个对象和一个函数做检测,如果对象继承自函数的原型,则该操作符返回真。例如:

1
2
var f = new Foo();
var isTrue = (f instanceof Foo);

作为详细一点的例子,假定我们使用和在 继承属性 中相同的一组定义。创建 Engineer 对象如下:

1
var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");

对于该对象,以下所有语句均为真:

1
2
3
4
5
chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;

基于此,可以写出一个如下所示的 instanceOf 函数:

1
2
3
4
5
6
7
8
9
10
11
function instanceOf(object, constructor) {
while (object != null) {
if (object == constructor.prototype)
return true;
if (typeof object == 'xml') {
return constructor.prototype == XML.prototype;
}
object = object.__proto__;
}
return false;
}

Note: 在上面的实现中,检查对象的类型是否为 “xml” 的目的在于解决新近版本的 JavaScript 中表达 XML 对象的特异之处。如果您想了解其中琐碎细节,可以参考 bug 634150。

1
2
3
4
instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)

但如下表达式为假:

1
instanceOf (chris, SalesPerson)

构造器中的全局信息

在创建构造器时,在构造器中设置全局信息要小心。例如,假设希望为每一个雇员分配一个唯一标识。可能会为 Employee 使用如下定义:

1
2
3
4
5
6
7
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}

基于该定义,在创建新的 Employee 时,构造器为其分配了序列中的下一个标识符。然后递增全局的标识符计数器。因此,如果,如果随后的语句如下,则 victoria.id 为 1 而 harry.id 为 2:

1
2
var victoria = new Employee("Pigbert, Victoria", "pubs")
var harry = new Employee("Tschopik, Harry", "sales")

乍一看似乎没问题。但是,无论什么目的,在每一次创建 Employee 对象时,idCounter 都将被递增一次。如果创建本章中所描述的整个 Employee 层级结构,每次设置原型的时候,Employee 构造器都将被调用一次。假设有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}
function Manager (name, dept, reports) {...}
Manager.prototype = new Employee;
function WorkerBee (name, dept, projs) {...}
WorkerBee.prototype = new Employee;
function Engineer (name, projs, mach) {...}
Engineer.prototype = new WorkerBee;
function SalesPerson (name, projs, quota) {...}
SalesPerson.prototype = new WorkerBee;
var mac = new Engineer("Wood, Mac");

还可以进一步假设上面省略掉的定义中包含 base 属性而且调用了原型链中高于它们的构造器。即便在现在这个情况下,在 mac 对象创建时,mac.id 为 5。

依赖于应用程序,计数器额外的递增可能有问题,也可能没问题。如果确实需要准确的计数器,则以下构造器可以作为一个可行的方案:

1
2
3
4
5
6
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
if (name)
this.id = idCounter++;
}

在用作原型而创建新的 Employee 实例时,不会指定参数。使用这个构造器定义,如果不指定参数,构造器不会指定标识符,也不会递增计数器。而如果想让 Employee 分配到标识符,则必需为雇员指定姓名。在这个例子中,mac.id 将为 1。

没有多继承

某些面向对象语言支持多重继承。也就是说,对象可以从无关的多个父对象中继承属性和属性值。JavaScript 不支持多重继承。

JavaScript 属性值的继承是在运行时通过检索对象的原型链来实现的。因为对象只有一个原型与之关联,所以 JavaScript 无法动态地从多个原型链中继承。

在 JavaScript 中,可以在构造器函数中调用多个其它的构造器函数。这一点造成了多重继承的假象。例如,考虑如下语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Hobbyist (hobby) {
this.hobby = hobby || "scuba";
}
function Engineer (name, projs, mach, hobby) {
this.base1 = WorkerBee;
this.base1(name, "engineering", projs);
this.base2 = Hobbyist;
this.base2(hobby);
this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")

进一步假设使用本章前面所属的 WorkerBee 的定义。此时 dennis 对象具有如下属性:

1
2
3
4
5
dennis.name == "Doe, Dennis"
dennis.dept == "engineering"
dennis.projects == ["collabra"]
dennis.machine == "hugo"
dennis.hobby == "scuba"

dennis 确实从 Hobbyist 构造器中获得了 hobby 属性。但是,假设添加了一个属性到 Hobbyist 构造器的原型:

1
Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]

dennis 对象不会继承这个新属性。

关于Object.observe的知识

发表于 2017-02-19

Object.observe

当前MVC模式在Angular.JS等冲击下已经变成了View视图和Model模型两个, 控制器由事件驱动机制替代,见 :MVI是一种Reactive MVC。

那么为什么JS不能自然支持事件通知机制呢?ES7引入了Object.observe:

1
2
3
var person = {};j
var changesHandler = changes => console.log(changes);
Object.observe(person, changesHandler);

这样,当person的内容有更改,将自动触发changes函数,如下:

1
2
3
4
5
6
7
person = {
  name: " jdon"
}
  如果person的名称从jdon改为jdon.com:
person = {
  name: " jdon.com"
}

这种name内容变化,将触发函数changes输出:


1
2
3
4
5
6
7
[
    {
        "type":"update",
        "name":"name",
        "oldValue: "jdon"
    }
]

如果person增加内容:

1
2
3
4
person = {
  name: " jdon.com"
  employer: "banq"
}

激活函数输出:

1
2
3
4
5
6
[
    {
        "type":"new",
        "name":"employer",
    }
]

如果person删除内容employer,那么输出是:

1
2
3
4
5
6
7
[
    {
        "type":"delete",
        "name":"employer",
        "oldValue: "banq"
    }
]

解除观察是:

1
Object.unobserve(person, changesHandler);

为了防止事件风暴,ES7使用了 Event Loop Queue + Microtasks,当事件处理器EventHandler发出改变事件给ChangeHandler以后,ChangeHandler不会排队在队列后面,而是替换当前EventHandler位置,这样在浏览器渲染页面之前完成所有事件通知:

js7.png

三种Ajax方式

发表于 2017-01-08

一、原生 JS 实现 AJAX

JS 实现 AJAX 主要基于浏览器提供的 XMLHttpRequest(XHR)类,所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。

1. 获取XMLHttpRequest对象

1
2
// 获取XMLHttpRequest对象
var xhr = new XMLHttpRequest();

2. 发送一个 HTTP 请求

接下来,我们需要打开一个URL,然后发送这个请求。分别要用到 XMLHttpRequest 的 open() 方法和 send() 方法。

二、 jQuery 实现 AJAX

jQuery 作为一个使用人数最多的库,其 AJAX 很好的封装了原生 AJAX 的代码,在兼容性和易用性方面都做了很大的提高,让 AJAX 的调用变得非常简单。下面便是一段简单的 jQuery 的 AJAX 代码:

1
2
3
4
5
6
7
8
$.ajax({
method: 'POST',
url: '/api',
data: { username: 'admin', password: 'root' }
})
.done(function(msg) {
alert( 'Data Saved: ' + msg );
});

对比原生 AJAX 的实现,使用 jQuery 就异常简单了。当然我们平时用的最多的,是下面两种更简单的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// GET
$.get('/api', function(res) {
// do something
});
// POST
var data = {
username: 'admin',
password: 'root'
};
$.post('/api', data, function(res) {
// do something
});

三、Fetch API

使用 jQuery 虽然可以大大简化 XMLHttpRequest 的使用,但 XMLHttpRequest 本质上但并不是一个设计优良的 API: + 不符合关注分离(Separation of Concerns)的原则 + 配置和调用方式非常混乱 + 使用事件机制来跟踪状态变化 + 基于事件的异步模型没有现代的 Promise,generator/yield,async/await 友好

Fetch API 旨在修正上述缺陷,它提供了与 HTTP 语义相同的 JS 语法,简单来说,它引入了 fetch() 这个实用的方法来获取网络资源。

原生支持率并不高,幸运的是,引入下面这些 polyfill 后可以完美支持 IE8+:

  • 由于 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham**
  • 引入 Promise 的 polyfill: es6-promise**
  • 引入 fetch 探测库:fetch-detector**
  • 引入 fetch 的 polyfill: fetch-ie8**
  • 可选:如果你还使用了 jsonp,引入 fetch-jsonp**
  • 可选:开启 Babel 的 runtime 模式,现在就使用 async/await

1. 一个使用 Fetch 的例子

先看一个简单的 Fetch API 的例子 🌰 :

1
2
3
4
5
6
7
fetch('/api').then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(error) {
console.log('Oops, error: ', error);
});

使用 ES6 的箭头函数后:

1
2
3
fetch('/api').then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('Oops, error: ', error))

可以看出使用Fetch后我们的代码更加简洁和语义化,链式调用的方式也使其更加流畅和清晰。但这种基于 Promise 的写法还是有 Callback 的影子,我们还可以用 async/await 来做最终优化:

1
2
3
4
5
6
7
8
9
async function() {
try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch (error) {
console.log('Oops, error: ', error);
}
}

使用 await 后,写代码就更跟同步代码一样。await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try…catch 捕获。

Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。

2. 使用 Fetch 的注意事项

  • Fetch 请求默认是不带 cookie,需要设置 fetch(url, {credentials: ‘include’})`
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject

接下来将上面基于 XMLHttpRequest 的 AJAX 用 Fetch 改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
var options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: 'admin', password: 'root' }),
credentials: 'include'
};
fetch('/api', options).then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log('Oops, error: ', error))

Github Issue: 分别使用 XHR、jQuery 和 Fetch 实现 AJAX · Issue #15 · nodejh/nodejh.github.io

十张GIF弄懂递归等概念

发表于 2017-01-08

十张GIFs让你弄懂递归等概念

文章内容来自网络

图像(包括动图)是传递信息的一种高效方式,往往能增强表象、记忆与思维等方面的反应强度。所谓一图胜千言,说的就是这个道理。

今天为大家整理了十张动图GIFS,有助于认识循环、递归、二分检索等概念的具体运行情况。代码实例以Python语言编写。

一、循环

GIF 1:最简单的 while 循环

​GIF 1:最简单的 while 循环

GIF 2:带 if/else 的循环

GIF 2:带 if/else 的循环

二、递归

GIF 3:递归概念的直接演示

GIF 3:递归概念的直接演示

GIF 4:递归的代码示例

GIF 4:递归的代码示例

GIF 5:递归求斐波那契数列

GIF 5:递归求斐波那契数列

GIF 6:递归求阶乘(图里缩进有点问题,请忽略吧)

GIF 6:递归求阶乘

三、按值传递和按引用传递

GIF 7:按值传递和按引用传递的区别

GIF 7:按值传递和按引用传递的区别

四、线性检索和二分检索

GIF 8:线性检索和二分检索求 23 的位置

GIF 8:线性检索和二分检索求 23 的位置

GIF 9:线性检索和二分检索求 1 的位置

GIF 9:线性检索和二分检索求 1 的位置

GIF 10:二分检索树

GIF 10:二分检索树

持续集成交付部署

发表于 2016-12-24

持续集成

Continuous Integration

持续集成强调开发人员提交了新代码之后,立刻进行构建、(单元)测试、打包等步骤。根据反馈的测试结果,我们可以知道新代码和原有代码能否正确地集成在一起。

持续交付

Continuous Delivery

持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境( production-like environments )」中。比如,我们完成单元测试后,可以把代码部署到连接数据库的 Staging 环境中进行更多的测试。如果代码没有问题,接下来就可以继续手动部署到生产环境中。

持续部署

Continuous Deployment

持续部署则是在持续交付的基础上,把部署到生产环境的过程自动化。

我个人觉得持续集成、持续交付、持续部署非常值得推广。开发过程中最怕集成时遇到问题导致返工,而持续集成、持续交付、持续部署恰恰可以做到问题早发现早解决,从而可以避免这样的麻烦。另外,持续集成、持续交付、持续部署的流程高度依赖自动化工具,所以这种开发方法也可以大大提高开发人员的工作效率。

21副GIF动图让你了解各种数学概念

“让我们面对它;总的来说数学是不容易的,但当你征服了问题,并达到新的理解高度,这就是它给你的回报。”

——Danica McKellar

数学是很难的科学,但因为它是科学家用数学来解释宇宙的语言,我们无可避免的要学习它。看看下面的这些GIF动图,它们提供了视觉的方式来帮助你理解各种数学技巧。

1、椭圆的画法

img

2、杨辉三角问题(Pascal triangles)解法

img

3、使用“FOIL”轻松的解决二项式乘法

img

4、对数解法技巧

img

5、矩阵转置的技巧

img

6、勾股定理

img

7、多边形的外角之和总是等于360度

img

8、圆周率π

img

9、一弧度就是长度刚好等于半径的一段圆弧所对的圆心角

img

10、在Y轴上使用正弦(红色),在X轴上使用余弦(蓝色),则在XY轴平面上画出的环形如下图(黑色)

img

11、同前一原理,但更简单

img

12、这是将sin和cos运用到三角形上

img

13、余弦是正弦的衍生物

img

14、正切线

img

15、同上,但翻个面看,更容易理解

img

16、将一个公式从笛卡尔坐标转换成轴坐标

img

17、画抛物线

img

18、黎曼和(Riemann sum)约等于其曲线下的面积

img

19、双曲线

img

20、将双曲线表现成3D形式,也许你不相信,它完全是用直线画成的

img

你甚至可以做成这样的效果:

img

趣图三幅:负载均衡算法需要改进

图一:负载均衡算法需要改进

负载均衡算法

图二:开发人员 vs 测试人员,一张很诡异的图片

开发人员 vs 测试人员

爱喝啤酒的程序员是如何学习数据结构的

如果在相亲时你说你是程序员,对方——一般是女的——会投来异样的眼光。程序员在其他人的眼中的形象一般是思维逻辑有问题,或木纳,或有点儿轴,或是书呆子。但凡事都在变化,程序员也在变化,英语中的Programmer这个词在国外现在流行写出Brogrammer。Brogrammer和Programmer的区别是,前者很时髦、很酷。Brogrammer有很多典型特征,比如说编程时戴墨镜,听摇滚乐,喜欢参加派对,喜欢啤酒等,如果你想知道你自己是否符合Brogrammer标准,你可以到这里测试一下自己。

一般来说,程序员喝酒是大忌,因为编程需要用脑子,要逻辑清晰。但就像之前说的,凡事都在进化,一方面,编程环境、编程工具越来越发达完善,对程序员的要求越来越低,另一方面,有些人喝完酒后更兴奋,脑子更活。下面这几幅图描绘的是一个爱喝啤酒的程序员是如何学习数据结构的,你可以看出,他酒喝了,数据结构也掌握了。

二叉树

二叉树

不平衡树

不平衡树

重新平衡树

重新平衡树

数组

数组

矩阵

矩阵

链接表

链接表

稀疏矩阵

稀疏矩阵

堆

堆

栈

栈

JS数据类型判断的方式

发表于 2015-04-10

一、typeof

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(typeof 123456); // number
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof '123456'); // string
console.log(typeof ''); // string
console.log(type new String); // object
console.log(typeof new Object); // object
console.log(typeof {}); // object
console.log(typeof function(){}); // function
console.log(typeof []); // object
console.log(typeof true); // boolean
console.log(typeof NaN); // number
console.log(typeof /^[-+]?\d+$/); // object

使用typeof检测数据类型,首先返回的都是一个字符串,其次字符串中包含了对应的数据类型,例如:”number”、”string”、”boolean”、”undefined”、”function”、”object”

  • typeof (引用类型) 除了函数, 都是 ‘object’,比如 typeof /123/

  • typeof null 为’object’

  • typeof undefined 为 ‘undefined’,通常, 如果使用两等号, null == undefined 为真.

  • 转换为数字的常见用法 “10”-0或+”10”, 如果没有转换成功,返回NaN,由于NaN 的一个特性: NaN != NaN,故判断转换成功与否的常见做法: (这也是我参见 jQuery的源码发现的,jQuery源码读100遍都不为过)

    1
    2
    ("10x" - 0) == ("10x" - 0);
    // 结果为假!

面试题:

1
console.log(typeof typeof typeof function () {}); // string

typeof的局限性:不能具体的细分是数组还是正则,还是对象中其他的值,因为使用typeof检测数据类型,对于对象数据类型中的所有的值,最后返回的结果都是”object”。

应用一:添加默认值

1
2
3
4
5
6
7
8
9
10
function fn(num1, num2) {
// 方式一
// if (typeof num2 === 'undefined') {
// num2 = 0;
// }
// 方式二:不适用fn(10,false)这种情况
num2 = num2 || 0;
}
fn(10);

应用二:回调函数调用

1
2
3
4
5
6
7
function fn(callback) {
//typeof callback === 'function' ? callback() : null;
callback && callback();
}
fn(function () {
});

二、instanceof

用于判断一个变量是否某个对象的实例,或用于判断一个变量是否某个对象的实例;

1
2
3
4
5
6
7
var ary = [];
console.log(ary instanceof Array); // true
console.log(ary instanceof Object); // true
function fn() {}
console.log(fn instanceof Function); // true
console.log(fn instanceof Object); // true

只要在当前实例的原型链上,用instanceof检测出来的结果都是true,所以在类的原型继承中,最后检测出来的结果未必是正确的,例如:

1
2
3
4
5
function Fn() {
}
Fn.prototype = new Array; // 原型继承:让子类的原型等于父类的一个实例
var f = new Fn;
console.log(f instanceof Array); // true

三、constructor

用于判断一个变量的原型,constructor 属性返回对创建此对象的数组函数的引用

Javascript中对象的prototype属性的解释是:返回对象类型原型的引用

constructor即构造函数,作用和instanceof非常的相似。

1
2
3
var obj = [];
console.log(obj.constructor === Array); // true
console.log(obj.constructor === RegExp); // false

constructor可以处理基本数据类型的检测

1
2
var num = 1;
console.log(num.constructor === Number); // true

constructor检测Object和instanceof不一样,一般情况下是检测不了的

1
2
3
var reg = /^$/;
console.log(reg.constructor === RegExp); // true
console.log(reg.constructor === Object); // false

constructor的局限性:我们可以把类的原型进行重写,在重写的过程中,很有可能把之前的constructor给覆盖了,这样检测出来的结果就是不准确的。

1
2
3
4
5
function Fn() {
}
Fn.prototype = new Array;
var f = new Fn;
console.log(f.constructor); // Array

对于特殊的数据类型null和undefined,它们的所属类型是Null和Undefined,但是浏览器把这两个类保护起来了,不允许在外面访问使用。

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
console.log("----------------Number---------------");
var A = 123;
console.log(A instanceof Number); //false
console.log(A.constructor == Number); //true
console.log(A.constructor);
console.log("----------------String---------------");
var B = "javascript";
console.log(B instanceof String); //false
console.log(B.constructor == String); //true
console.log(B.constructor);
console.log("----------------Boolean---------------");
var C = true;
console.log(C instanceof Boolean); //false
console.log(C.constructor == Boolean); //true
console.log(C.constructor);
console.log("----------------null---------------");
var D = null;
console.log(D instanceof Object); //false
//console.log(D.constructor == null); //报错
//console.log(D.constructor); //报错
console.log("----------------undefined---------------");
var E = undefined;
//console.log(E instanceof undefined); // //报错
//console.log(E.constructor == undefined); //报错
//console.log(E.constructor); //报错
console.log("----------------function---------------");
var F = function() {};
console.log(F instanceof Function);
console.log(F.constructor == Function);
console.log(F.constructor);
console.log("----------------new function---------------");
function SB() {};
var G = new SB();
console.log(G instanceof SB);
console.log(G.constructor == SB);
console.log(G.constructor);
console.log("----------------new Object---------------");
var H = new Object;
console.log(H instanceof Object);
console.log(H.constructor == Object);
console.log(H.constructor);
console.log("-----------------Array--------------");
var I = [];
console.log(I instanceof Array);
console.log(I.constructor == Array);
console.log(I.constructor);
console.log("-----------------JSON--------------");
var J = {
"good": "js",
"node": "very good"
};
console.log(J instanceof Object);
console.log(J.constructor == Object);
console.log(J.constructor);

四、Object.prototype.toStrong.call()

toString的理解:

  • 咋一看应该是转换为字符串,但是某些toString方法不仅仅是转换为字符串。对于Number、String、Boolean、Array、RegExp、Date、Function原型上的toString方法都是把当前的数据类转换为字符串的类型(它们的作用仅仅是用来转换为字符串的)

  • Object.prototype.toString并不是用来转换为字符串的。

    1
    2
    ({name:"iceman"}).toString() // --> "[object Object]"
    Math.toString() // --> "[object Math]"

Number.prototype.toString是转换字符串的

1
2
3
console.log((1).toString()); // --> Number.prototype.toString,--> "1" 转换为字符串
console.log((1).__proto__.__proto__.toString()); // Object.prototype.toString --> "[object Object]"
console.log((128).toString(2/8/10)); // 把数字转换为二进制/八进制/十进制

Object.prototype.toStrong.call()是检测数据类型最准确最常用的方式,起原理为:

  • 先获取Object原型上的toString方法,让方法执行,并且改变方法中的this关键字的指向;
  • Object.prototype.toString 它的作用是返回当前方法的执行主体(方法中this)所属类的详细信息;
1
2
3
4
5
6
7
8
var obj = {name:'iceman'};
// toString中的this是obj,返回的是obj所属类的信息 --> "[object Object]"
// 第一个object代表当前实例是对象数据类型的(这个是固定死的)
// 第二个Object,代表的是obj所属的类是Object
console.log(obj.toString());
// toString中的this是Math,那么返回的是Math所属类的信息 --> "[object Math]"
console.log(Math.toString());

检测其他类型:

1
2
3
4
5
6
7
8
9
10
var ary = [];
console.log(Object.prototype.toString.call(ary)); // --> "[object Array]"
console.log(Object.prototype.toString.call(/^$/)); // --> "[object RegExp]"
console.log(({}).toString.call(1)); // --> "[object Number]"
console.log(({}).toString.call('珠峰')); // --> "[object String]"
console.log(({}).toString.call(true)); // --> [object Boolean]
console.log(({}).toString.call(undefined)); // -->[object Undefined]
console.log(({}).toString.call(null)); // -->[object Null]
console.log(({}).toString.call(function () {})); // -->[object Function]

实际使用:

1
2
3
4
5
var ary = [];
console.log(Object.prototype.toString.call(ary) === '[object Array]'); // true
var reg = /^\[object Array\]$/;
console.log(reg.test(Object.prototype.toString.call(ary))); // true

检测原型继承的情况:

1
2
3
4
5
6
function Fn() {
}
Fn.prototype = new Array;
var f = new Fn;
console.log(f instanceof Array); // true
console.log(Object.prototype.toString.call(f) === '[object Array]'); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log({}.toString.call(1)); // [object Number]
console.log({}.toString.call("11")); // [object String]
console.log({}.toString.call(/123/)); // [object RegExp]
console.log({}.toString.call({})); // [object Object]
console.log({}.toString.call(function() {})); // [object Function]
console.log({}.toString.call([])); // [object Array]
console.log({}.toString.call(true)); // [object Boolean]
console.log({}.toString.call(new Date())); // [object Date]
console.log({}.toString.call(new Error())); // [object Error]
console.log({}.toString.call(null)); // [obejct Null]
console.log({}.toString.call(undefined)); // [object Undefined]
console.log(String(null)); // null
console.log(String(undefined)); // undefind

使用jQuery中的方法$.type()

现在看看jQuery是怎么做的

1
2
3
4
5
6
7
8
// 先申明一个对象,目的是用来做映射
var class2type = {};
// 申明一个core_toString() 的方法,得到最原始的toString() 方法,因为在很多对象中,toStrintg() 已经被重写
var core_toString() = class2type.toString;
// 这里为 toStrintg() 后的结果和类型名做一个映射,申明一个core_toString() 后的结果,而值就是类型名
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log($.type(1)); // number
console.log($.type("11")); // string
console.log($.type(/123/)); // regexp
console.log($.type({})); // object
console.log($.type(function() {})); // function
console.log($.type([])); // array
console.log($.type(true)); // boolean
console.log($.type(new Date())); // date
console.log($.type(new Error())); // error
console.log($.type(null)); // null
console.log($.type(undefined)); // undefined
console.log(String(null)); // null
console.log(String(undefined)); // undefined

上面的打印结果与

1
class2type[ "[object " + name + "]" ] = name.toLowerCase();

不谋而合!

这是jQuery.type 的核心方法

1
2
3
4
5
6
7
8
9
type: function( obj ) {
if ( obj == null ) {
return String( obj );
}
// Support: Safari <= 5.1 (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ core_toString.call(obj) ] || "object" :
typeof obj;
},

注意,为什么把 null 或者 undefined 单独讨论呢,因为 在一些版本浏览器中

1
2
3
> console.log(core_toString.call(null));
> console.log(core_toString.call(undefined));
>

>

这是会报错的!

​ 如果是对象类型,另:由于 在一些低版本的浏览器中,typeof /123/ 会返回的是 “function” 而不是 “object”,所以这里要判断是否是函数,要明白 这里的 typeof obj === function 不是为了函数讨论的,因为函数本身就可以通过typeof 来得到类型.

1
2
typeof obj === "object" || typeof obj === "function" ?
class2type[ core_toString.call(obj) ]

就直接返回class2type 中键值对的结果,,如果不是,那么一定就是基本类型, 通过 typeof 就可以啦.

1
2
class2type[ core_toString.call(obj) ] || "object" :
// 这是防止一些未知情况的,如果未取到,就返回object

但是 jQuery.type 有一个很大的缺陷

这是一个自定义类型

1
2
3
4
5
6
function Person() {
this.name = 'pawn';
}
var p = new Person();
console.log($.type(p));
console.log({}.toString.call(p));

// 注意,这里会打印 [object Object],通过上面的方法,无法得到精确的自定义类型
这也是 它的一个大缺陷了!

img

下面,我们通过构造函数的方式来获取精确类型

通过构造函数来获取类型

在理解这个方法之前,需要理解两个点

prorotype 原型属性

我们知道,任何对象或者函数都直接或者间接的继承自Object 或者 Function, (其实最终Function 是继承自 Object 的,这属于原型链的知识了,见下图)。那么,任何一个对象都具有原型对象 proto (这个对象只在chrome 和 firefox 暴露,但是在其他浏览器中也是存在的),这个原型对象就是这个对象的构造函数的原型属性(这里可能有点绕,直接上图).

img

由于 任何函数都具有 原型属性prototype,并且这个原型属性具有一个默认属性 constructor,它是这个函数的引用,看下面的代码

1
2
3
4
function Person(){
this.name = 'pawn';
}
console.log(Person.prototype.constructor === Person); //true

发现,这两个东西其实一个东西

但是,在某些情况下,需要这么写

1
2
3
4
5
6
7
8
function Person(){
this.name = 'pawn';
}
Person.protype = {
XX: ... ,
xx: ... ,
...
}

这么做,就会覆盖原本的 protype 方法,那么construcor 就不存在了,这是,必须要显示的申明这个对象,

construction: Person, 这句话非常重要,作用是修正this指向

1
2
3
4
5
6
Person.protype = {
construction: Person, //这句话的作用是修正this指向
XX: ... ,
xx: ... ,
...
}

在jQuery的中,就是这么做的,

1
2
3
4
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
var match, elem;

关于 jQuery对象封装的方式 也是非常值得研究

img

注意,这里已经不是熟悉 [object Object],而是 已经重写了.

也就是,如果调用一个函数的toString() 方法.那么就会打印这个函数的函数体.

img

如何通过构造函数来获得变量的类型?

判断是否是基本类型

1
2
3
4
5
6
7
8
9
10
11
var getType = function(obj){
if(obj == null){
return String(obj);
}
if(typeof obj === 'object' || typeof obj === 'fucntion'){
...
}else{
// 如果不是引用类型,那么就是基本类型
return typeof obj
}
}

如果是对象或者函数类型

1
2
3
4
5
function Person(){
this.name = 'pawn';
}
var p = new Person();
console.log(p.constructor); //返回function Person(){...}

现在要做的事 : 如何将Person 提取出来呢?
毋庸置疑,字符串切割那一套肯定可以办到,但是太 low 啦!
这里,我使用正则将Person提取出来

1
var regex = /function\s(.+?)\(/
1
2
3
4
5
6
7
function Person(){
this.name = 'pawn';
}
var p = new Person();
var c = p.constructor
var regex = /function\s(.+?)\(/;
console.log('|' + regex.exec(c)[1] + '|');

img

其实,除了上面的正则,每个函数还有一个name属性**,返回函数名,但是ie8 是不支持的.

因此上面的代码可以写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var getType = function(obj){
if(obj == null){
return String(obj);
}
if(typeof obj === 'object' || typeof obj === 'function'){
var constructor = obj.constructor;
if(constructor && constructor.name){
return constructor.name;
}
var regex = /function\s(.+?)\(/;
return regex.exec(c)[1];
}else{
// 如果不是引用类型,那么就是基本;类型
return typeof obj;
}
};

但是上面的代码太丑啦,将其简化

简化

1
2
3
4
5
6
7
8
9
10
11
12
var getType = function(obj){
if(obj == null){
return String(obj);
}
if(typeof obj === 'object' || typeof obj === 'function'){
return obj.constructor && obj.constructor.name.toLowerCase() ||
/function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase();
}else{
// 如果不是引用类型,那么就是基本类型
return typeof obj;
}
};

还是比较麻烦,继续简化

1
2
3
4
5
6
7
8
9
var getType = function(obj){
if(obj == null){
return String(obj);
}
return typeof obj === 'object' || typeof obj === 'function' ?
obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase() ||
/function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase():
typeof obj;
};

好了,已经全部弄完了,写个代码测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){
this.name = 'pawn';
}
var p = new Person();
console.log(getType(p));
console.log(getType(1));
console.log(getType("a"));
console.log(getType(false));
console.log(getType(/123/));
console.log(getType({}));
console.log(getType(function(){}));
console.log(getType(new Date()));
console.log(getType(new Error()));
console.log(getType( null));
console.log(getType( undefined));

img

img

img

img

1.有时会看到Object.prototype.toString.call()

img

2.toString()是一个怎样的方法,他定义在哪里?

img


3.call.apply.bind可以吗?

img


4.为神马要去call呢?用 Object.prototype.toString.call(obj) 而不用 obj.toString() 呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
function A(){
this.say=function(){
console.log("我是1");
}
}
function B(){
this.say=function(){
console.log("我是2");
}
}
var a=new A();
var b=new B();
a.say.call(b); //我是1
</script>
</head>
<body>
</body>
</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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
function A(){
this.name='SB';
this.say=function(){
console.log("我是1");
}
}
function B(){
A.call(this); //B继承A,重写say方法
this.say=function(){
console.log("我是2");
}
}
var a=new A();
var b=new B();
console.log(b.name); //SB
b.say(); //我是2
a.say.call(b); //我是1
</script>
</head>
<body>
</body>
</html>

就是怕你重写了toString,所以才要用object 最原始的他toString,所以才去call。

5.Object.prototype.toString方法的原理是什么?

参考链接:http://www.jb51.net/article/79941.htm

在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法.

12var arr = [];console.log(Object.prototype.toString.call(arr)) //“[object Array]”

本文要讲的就是,toString方法是如何做到这一点的,原理是什么.

ECMAScript 3

在ES3中,Object.prototype.toString方法的规范如下:

115.2.4.2 Object.prototype.toString()

在toString方法被调用时,会执行下面的操作步骤:

\1. 获取this对象的[[Class]]属性的值.

\2. 计算出三个字符串”[object “, 第一步的操作结果Result(1), 以及 “]”连接后的新字符串.

\3. 返回第二步的操作结果Result(2).

[[Class]]是一个内部属性,所有的对象(原生对象和宿主对象)都拥有该属性.在规范中,[[Class]]是这么定义的

[[Class]]一个字符串值,表明了该对象的类型.


然后给了一段解释:

所有内置对象的[[Class]]属性的值是由本规范定义的.所有宿主对象的[[Class]]属性的值可以是任意值,甚至可以是内置对象使用过的[[Class]]属性的值.[[Class]]属性的值可以用来判断一个原生对象属于哪种内置类型.需要注意的是,除了通过Object.prototype.toString方法之外,本规范没有提供任何其他方式来让程序访问该属性的值(查看 15.2.4.2).

也就是说,把Object.prototype.toString方法返回的字符串,去掉前面固定的”[object “和后面固定的”]”,就是内部属性[[class]]的值,也就达到了判断对象类型的目的.jQuery中的工具方法$.type(),就是干这个的.

在ES3中,规范文档并没有总结出[[class]]内部属性一共有几种,不过我们可以自己统计一下,原生对象的[[class]]内部属性的值一共有10种.分别是:”Array”, “Boolean”, “Date”, “Error”, “Function”, “Math”, “Number”, “Object”, “RegExp”, “String”.

ECMAScript 5

在ES5.1中,除了规范写的更详细一些以外,Object.prototype.toString方法和[[class]]内部属性的定义上也有一些变化,Object.prototype.toString方法的规范如下:

15.2.4.2 Object.prototype.toString ( )

在toString方法被调用时,会执行下面的操作步骤:


如果this的值为undefined,则返回”[object Undefined]”.

如果this的值为null,则返回”[object Null]”.

让O成为调用ToObject(this)的结果.

让class成为O的内部属性[[Class]]的值.

返回三个字符串”[object “, class, 以及 “]”连接后的新字符串.


可以看出,比ES3多了1,2,3步.第1,2步属于新规则,比较特殊,因为”Undefined”和”Null”并不属于[[class]]属性的值,需要注意的是,这里和严格模式无关(大部分函数在严格模式下,this的值才会保持undefined或null,非严格模式下会自动成为全局对象).第3步并不算是新规则,因为在ES3的引擎中,也都会在这一步将三种原始值类型转换成对应的包装对象,只是规范中没写出来.ES5中,[[Class]]属性的解释更加详细:

所有内置对象的[[Class]]属性的值是由本规范定义的.所有宿主对象的[[Class]]属性的值可以是除了”Arguments”, “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, “String”之外的的任何字符串.[[Class]]内部属性是引擎内部用来判断一个对象属于哪种类型的值的.需要注意的是,除了通过Object.prototype.toString方法之外,本规范没有提供任何其他方式来让程序访问该属性的值(查看 15.2.4.2).

和ES3对比一下,第一个差别就是[[class]]内部属性的值多了两种,成了12种,一种是arguments对象的[[class]]成了”Arguments”,而不是以前的”Object”,还有就是多个了全局对象JSON,它的[[class]]值为”JSON”.第二个差别就是,宿主对象的[[class]]内部属性的值,不能和这12种值冲突,不过在支持ES3的浏览器中,貌似也没有发现哪些宿主对象故意使用那10个值.

ECMAScript 6

ES6目前还只是工作草案,但能够肯定的是,[[class]]内部属性没有了,取而代之的是另外一个内部属性[[NativeBrand]].[[NativeBrand]]属性是这么定义的:

内部属性属性值描述
[[NativeBrand]]枚举NativeBrand的一个成员.该属性的值对应一个标志值(tag value),可以用来区分原生对象的类型.

[[NativeBrand]]属性的解释:

[[NativeBrand]]内部属性用来识别某个原生对象是否为符合本规范的某一种特定类型的对象.[[NativeBrand]]内部属性的值为下面这些枚举类型的值中的一个:NativeFunction, NativeArray, StringWrapper, BooleanWrapper, NumberWrapper, NativeMath, NativeDate, NativeRegExp, NativeError, NativeJSON, NativeArguments, NativePrivateName.[[NativeBrand]]内部属性仅用来区分区分特定类型的ECMAScript原生对象.只有在表10中明确指出的对象类型才有[[NativeBrand]]内部属性.

表10 — [[NativeBrand]]内部属性的值

属性值对应类型
NativeFunctionFunction objects
NativeArrayArray objects
StringWrapperString objects
BooleanWrapperBoolean objects
NumberWrapperNumber objects
NativeMathThe Math object
NativeDateDate objects
NativeRegExpRegExp objects
NativeErrorError objects
NativeJSONThe JSON object
NativeArgumentsArguments objects
NativePrivateNamePrivate Name objects

可见,和[[class]]不同的是,并不是每个对象都拥有[[NativeBrand]].同时,Object.prototype.toString方法的规范也改成了下面这样:

15.2.4.2 Object.prototype.toString ( )

在toString方法被调用时,会执行下面的操作步骤:

如果this的值为undefined,则返回”[object Undefined]”.

如果this的值为null,则返回”[object Null]”.

让O成为调用ToObject(this)的结果.

如果O有[[NativeBrand]]内部属性,让tag成为表29中对应的值.

否则

让hasTag成为调用O的[[HasProperty]]内部方法后的结果,参数为@@toStringTag.

如果hasTag为false,则让tag为”Object”.

否则,

让tag成为调用O的[[Get]]内部方法后的结果,参数为@@toStringTag.

如果tag是一个abrupt completion,则让tag成为NormalCompletion(“???”).

让tag成为tag.[[value]].

如果Type(tag)不是字符串,则让tag成为”???”.

如果tag的值为”Arguments”, “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”,或

者”String”中的任一个,则让tag成为字符串”~”和tag当前的值连接后的结果.

返回三个字符串”[object “, tag, and “]”连接后的新字符串.

表29 — [[NativeBrand]] 标志值

[[NativeBrand]]值标志值
NativeFunction”Function”
NativeArray”Array”
StringWrapper”String”
BooleanWrapper”Boolean”
NumberWrapper”Number”
NativeMath”Math”
NativeDate”Date”
NativeRegExp”RegExp”
NativeError”Error”
NativeJSON”JSON”
NativeArguments”Arguments”

可以看到,在规范上有了很大的变化,不过对于普通用户来说,貌似感觉不到.

也许你发现了,ES6里的新类型Map,Set等,都没有在表29中.它们在执行toString方法的时候返回的是什么?

``

console.log(Object.prototype.toString.call(Map())) //"[object Map]"

console.log(Object.prototype.toString.call(Set())) //"[object Set]"

其中的字符串”Map”是怎么来的呢:

15.14.5.13 Map.prototype.@@toStringTag

@@toStringTag 属性的初始值为字符串”Map”.

由于ES6的规范还在制定中,各种相关规定都有可能改变,所以如果想了解更多细节.看看下面这两个链接,现在只需要知道的是:[[class]]没了,使用了更复杂的机制.

以上所述是JavaScript中Object.prototype.toString方法的原理

  • 数据类型检测的四种方式 - 简书
  • JS类型判断 - TalkingCoder

Immutable.js和函数式编程概念介绍

发表于 2015-03-26

​ 了解功能数据结构及其在Facebook的流行图书馆JavaScript概述中的用途:Immutable.js

功能规划在过去几年一直在上升。诸如Clojure,Scala和Haskell之类的语言使命令式程序员的眼睛带来了一些有趣的技术,可以在某些用例中提供显着的好处。Immutable.js旨在通过一个简单直观的API为JavaScript带来一些好处。跟随我们通过这个概述,了解一些这些好处,以及如何让他们在你的项目计数!


介绍:不变性和Immutable.js的情况

虽然函数式编程不仅仅是不变性,许多函数式语言非常强调不可变性。一些,如Clean和Haskell,对数据可以被突变的方式和时间设置硬编译时限制。许多开发商都被这个推迟了。对于那些忍受最初冲击的人,开始出现解决问题的新模式和方法。特别地,数据结构是新人对功能范例的主要冲突点。

最后,不可变对可变数据结构的问题归结为冷,硬的数学。算法分析告诉我们哪些数据结构最适合不同类型的问题。然而,语言支持可以在很大程度上帮助使用和实现这些数据结构。JavaScript,由于是一种多范式语言,为可变和不可变的数据结构提供了肥沃的基础。其他语言,如C,可以实现不可变的数据结构。然而,语言的限制可以使其使用麻烦。

那么什么是一个突变呢?变异是对数据或包含它的数据结构的原位更改。不变性,另一方面,每当需要改变时,这样的数据和数据结构的副本。

不变树

从维基百科采取的图象。

那么什么是功能数据结构的原则,特别是什么使得不可变性如此重要?此外,什么是他们的正确的用例?这些是我们将在下面探讨的一些问题。

注意:您可能不知道这一点,但您可能已经在您的JavaScript代码中使用某些函数式编程结构。例如,Array.map对数组中的每个项应用一个函数并返回一个新数组,而不修改过程中的原始数据。函数式编程作为一个范例,支持第一类函数,可以传递给返回现有数据的新版本的算法。这其实是什么Array.map。这种处理数据的方式有利于组合,在功能编程中的另一个核心概念。

关键概念

这些是功能编程背后的一些关键概念。希望在本文中,您将了解这些概念如何适用于Immutable.js和其他函数库的设计和使用。

不变性

不变性是指数据(以及管理它的数据结构)在实例化之后的行为:不允许突变。在实践中,突变可以分为两组:可见突变和不可见突变。可见突变是修改数据或包含它的数据结构的方式,可以通过API 由外部观察者注意到。另一方面,隐形突变**是不能通过API注意到的变化(缓存数据结构是这方面的一个很好的例子)。在某种意义上,不可见的突变可以被认为是副作用(我们探索这个概念和它的意思下面)。**

1
2
3
4
var list1 = Immutable.List.of(1, 2);
// We need to capture the result through the return value:
// list1 is not modified!
var list2 = list1.push(3, 4, 5);

有趣的好处出现在开发人员(和编译器/运行时)可以肯定数据不能改变:

  • 多线程锁定不再是一个问题:由于数据不能更改,因此不需要锁来同步多个线程。
  • 持久性(下面探讨的另一个关键概念)变得更容易。
  • 复制成为一个常量操作:复制只是创建对数据结构的现有实例的新引用。
  • 在某些情况下可以优化值比较:当运行时或编译器可以确保在加载或编译期间某个实例仅在指向同一引用时相等时,深值比较可以成为引用比较。这被称为实习,通常只适用于在编译或加载时可用的数据。这种优化也可以手动执行(与React和Angular一样,在最后的旁边部分解释)。

您已经使用了不可变的数据结构:字符串

JavaScript中的字符串是不可变的。String原型中的所有方法都执行读取操作或返回新字符串。

一些JavaScript运行时利用它来执行实现:在加载时或在JIT编译期间,运行时可以简化字符串比较(通常在字符串文字之间)到简单的引用比较。您可以检查浏览器如何使用简单的JSPerf测试用例处理此问题。检查相同测试的其他修订版本以获得更全面的测试用例。

Firefox 45字符串在Linux上的实习结果

不可变性和OBJECT.FREEZE()

JavaScript是一种动态的弱类型语言(如果你熟悉编程语言理论,则是无类型的)。因此,有时难以对对象和数据实施某些约束。Object.freeze()在这方面有帮助。调用将Object.freeze所有属性标记为不可变。分配将静默失败或抛出异常(在严格模式下)。如果你正在写一个不可变的对象,调用Object.freeze后的建设可以帮助。

牢记Object.freeze()是浅:子对象的属性可以修改。为了解决这个问题,Mozilla显示了如何deepFreeze编写这个函数的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deepFreeze(obj) {
// Retrieve the property names defined on obj
var propNames = Object.getOwnPropertyNames(obj);
// Freeze properties before freezing self
propNames.forEach(function(name) {
var prop = obj[name];
// Freeze prop if it is an object
if (typeof prop == 'object' && prop !== null) {
deepFreeze(prop);
}
});
// Freeze self (no-op if already frozen)
return Object.freeze(obj);
}

副作用

在编程语言理论中,对任何操作(通常是函数或方法调用)的副作用是可以在调用函数之外看到的可观察效果。换句话说,可以在执行呼叫之后找到状态的改变。每个调用都会改变一些状态。与通常与数据和数据结构相关的不变性的概念相反,副作用通常与整个程序的状态相关联。保留数据结构的实例的不变性的函数可能具有副作用。一个很好的例子是缓存函数或memoization。虽然对外部观察者,它可能看起来没有发生变化,更新全局或本地高速缓存具有更新用作高速缓存的内部数据结构(所得的加速也是副作用)的副作用。开发者的工作是了解这些副作用并适当地处理它们。

例如,按照高速缓存的示例,具有高速缓存作为前端的不可变数据结构不能再自由地传递到不同的线程。缓存必须支持多线程,否则可能会发生意外结果。

功能性编程作为范例有利于使用副作用自由功能。为了应用,函数必须仅对传递给它们的数据执行操作,并且这些操作的效果只应该被调用者看到。不变的数据结构与副作用自由功能是相辅相成的。

“不变的数据结构与副作用自由功能是相辅相成的。TWEET这个 img

1
2
3
4
5
6
7
8
9
10
11
12
13
var globalCounter = 99;
// This function changes global state.
function add(a, b) {
++globalCounter;
return a + b;
}
// A call to the seemingly innocent add function above will produce potentially
// unexpected changes in what is printed in the console here.
function printCounter() {
console.log(globalCounter.toString());
}

纯度

纯度是可以强加在函数上的附加条件:纯函数仅依赖于作为参数传递给它们的结果。换句话说,纯函数不能依赖于通过其他构造可访问的全局状态或状态。

1
2
3
4
5
6
7
var globalValue = 99;
// This function is impure: its result will change if globalValue is changed,
// even when passed the same values in 'a' and 'b' as in previous calls.
function sum(a, b) {
return a + b + globalValue;
}

参考透明度

将副作用自由功能与纯度组合的结果是参照透明度。通过相同参数集合的透明函数可以在任何点通过其结果知道某些这种变化而被替换,而不是作为整体的计算。

正如您可能已经注意到的,每个这些条件对数据和代码的行为有更高的限制。虽然这导致灵活性降低,但是当涉及分析和证明时,实现了深厚的收益。可轻易地证明,不具有副作用的不变数据结构可以传递到不同的线程,而不用担心锁定。

1
2
3
4
5
6
7
function add(a, b) {
return a + b;
}
// The following call can be replaced by its result: 3. This is possible because
// it is referentially transparent. IOW, side-effect free and pure.
var r1 = add(1, 2); // r1 = 3;

持久性

正如我们在上一节中看到的,不变性使某些事情更容易。使用不可变的数据结构变得更容易的另一个事情是持久性。持久性,在数据结构的上下文中,指的是在构建新版本之后保持数据结构的较旧版本的可能性。

正如我们之前提到的,当对不可变数据结构执行写操作时,不是改变结构本身或其数据,而是返回新版本的结构。然而,大多数时候,关于数据或数据结构的大小的修改很小。因此,执行整个数据结构的完整副本是次优的。大多数不可变的数据结构算法利用第一版本数据的不变性,仅执行需要改变的数据(以及数据结构的部分)的副本。

部分持久性数据结构是支持对其最新版本的修改以及对所有先前版本的数据的只读操作的那些。完全持久的数据结构允许对所有版本的数据进行读写。注意,在所有情况下,写入或修改数据意味着创建数据结构的新版本。

它可能不是完全明显的,但持久的数据结构有利于垃圾收集,而不是引用计数或手动内存管理。由于每个更改都会导致新版本的数据,并且先前版本必须可用,因此每次执行更改时,都会创建对现有数据的新引用。在手动存储器管理方案上,跟踪哪些数据片段具有引用快速地变得麻烦。另一方面,从开发人员的角度来看,引用计数使得事情更容易,但是从算法的角度来看效率低:每次执行改变时,必须更新改变的数据的引用计数。此外,这种看不见的变化实际上是副作用。因此,它限制了某些益处的适用性。垃圾收集,另一方面,没有这些问题。添加对现有数据的引用是免费的。

在以下示例中,原始列表自创建以来的每个版本都可用(通过每个变量绑定):

1
2
3
4
var list1 = Immutable.List.of(1, 2);
var list2 = list1.push(3, 4, 5);
var list3 = list2.unshift(0);
var list4 = list1.concat(list2, list3);

懒惰评价

另一个不那么明显的不变性的好处是更容易懒惰操作的形式。惰性操作是那些在执行这些操作的结果之前不执行任何操作的操作(通常通过严格的求值操作;严格与上下文中的惰性相反)。不可变性在惰性操作的上下文中非常有用,因为惰性求值通常需要在将来执行操作。如果与操作相关的数据在构造操作的时间和需要结果的时间之间以任何方式改变,则操作不能安全地执行。不可变数据有助于建立惰性操作,因为某些数据不会改变。换一种说法,

Immutable.js支持惰性操作:

1
2
3
4
5
var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
.filter(x => x % 2)
.map(x => x * x);
// Only performs as much work as necessary to get the first result
console.log(oddSquares.get(1)); // 9

延迟评估有几个好处。最重要的是不需要计算不必要的值。例如,考虑由元素1到10形成的列表。现在让我们对列表中的每个元素应用两个独立的操作。第一个操作将被调用plusOne,第二个操作被调用plusTen。这两个操作都明显:第一个添加一个到它的参数,第二个添加十。

1
2
3
4
5
6
7
8
9
function plusOne(n) {
return n + 1;
}
function plusTen(n) {
return n + 10;
}
var list = [1,2,3,4,5,6,7,8,9,10];
var result = list.map(plusOne).map(plusTen);

正如你可能已经注意到的,这是低效的:循环内部map运行两次,即使没有result使用任何元素。假设你只需要第一个元素:with strict evaluation两个循环完全运行。使用延迟评估,每个循环运行,直到返回请求的结果。换句话说,如果result[0]被请求,只执行每个plus...函数的一次调用。

延迟评估还可以允许无限数据结构。例如,如果支持延迟评估,则可以安全地表达从1到无穷大的序列。延迟评估也可以允许无效值:如果从不请求计算中的无效值,则不执行无效操作(这可能导致异常或其他错误条件)。

某些功能编程语言还可以在懒惰评估可用时执行高级优化,例如砍伐森林或循环融合。实质上,这些优化可以将根据多个循环定义的操作转换为单个循环,或者换句话说,移除中间数据结构。在实践中,map上面例子中的两个调用变成一个map调用plusOne并plusTen在同一循环中调用的单个调用。Nifty,嗯?

然而,并不是一切都对懒惰评价是好的:任何表达式被评估和计算执行的确切点停止显而易见。分析某些复杂的延迟操作可能相当困难。另一个缺点是空间泄漏:由于存储必要的数据以在将来执行给定的计算而导致的泄漏。某些惰性构造可以使此数据无限增长,这可能导致问题。

组成

在功能编程的上下文中的组合是指将不同功能组合成新的强大功能的可能性。第一类函数(可以作为数据处理并传递给其他函数的函数),闭包和currying(Function.bind对类固醇考虑)是必要的工具。JavaScript的语法不像某些函数式编程语言的语法一样方便,但它肯定是可能的。适当的API设计可以产生良好的效果。

Immutable的懒惰功能结合组合产生方便,可读的JavaScript代码:

1
2
3
4
5
6
Immutable.Range(1, Infinity)
.skip(1000)
.map(n => -n)
.filter(n => n % 2 === 0)
.take(2)
.reduce((r, n) => r * n, 1);

逃生舱口:突变

对于不变性可以提供的所有优点,某些操作和算法仅当突变可用时才有效。尽管不变性是大多数函数式编程语言(与命令式语言相反)的默认值,但是突变通常可能有效地实现这些操作。

再次,Immutable.js已覆盖:

1
2
3
4
var list1 = Immutable.List.of(1,2,3);
var list2 = list1.withMutations(function (list) {
list.push(4).push(5).push(6);
});

算法注意事项

在算法和数据结构领域,没有免费膳食。在一个领域的改进通常导致在另一个更糟的结果。不变性也不例外。我们已经讨论了不变性的一些好处:易持久性,更简单的推理,更少的锁定等; 但有什么缺点?

当谈论算法时,时间复杂性可能是你应该记住的第一件事。不可变数据结构具有与可变数据结构不同的运行时特性。特别地,不变的数据结构通常在考虑持久性需求时具有良好的运行时间特性。

这些差异的一个简单例子是单链表:通过使每个节点指向下一个节点(但不返回)而形成的列表。

可能的Immutable.js单链表的实现

基于Leslie Sanford的持久数据结构图的图。

可变单链表具有以下时间复杂度(最坏情况,假定前,后和插入节点是已知的):

  • 前缀:O(1)
  • 附加:O(1)
  • 插入:O(1)
  • 查找:O(n)
  • 副本:O(n)

相反,不变的单链表具有以下时间复杂度(最坏情况,假定前,后和插入节点是已知的):

  • 前缀:O(1)
  • 附加:O(n)
  • 插入:O(n)
  • 查找:O(n)
  • 复制:O(1)

如果你不熟悉时间分析和大O表示法,阅读这个。

这不为不可变的数据结构绘制好的画面。然而,最坏情况时间分析不考虑对持续需求的影响。换句话说,如果可变数据结构必须符合这个要求,运行时复杂性大多看起来像那些来自不可变版本(至少对于这些操作)。写时复制和其它技术可以改进一些操作的平均时间,这也不被考虑用于最坏情况分析。

在实践中,最坏情况分析可能不总是选择数据结构的最具代表性的时间分析形式。摊销分析将数据结构视为一组操作。具有良好的摊销时间的数据结构可以显示偶尔的最差时间行为,而在通常情况下保持得更好。分摊分析有意义的一个好例子是一个动态数组,当一个元素需要分配超过它的末尾时,它被优化为其大小的两倍。最坏情况分析为附加操作给出O(n)时间。摊销时间可以被认为是O(1),因为N / 2追加操作可以在单个追加产生O(n)时间之前执行。一般来说,如果您的用例需要确定性时间,则不能考虑摊销时间。

时间复杂度分析也忽略了其他重要的注意事项:某个数据结构的使用如何影响它周围的代码?例如,对于不可变的数据结构,在多线程场景中可能不需要锁定。

CPU高速缓存注意事项

另一个要记住的事情,特别是对于高性能计算,是数据结构与底层CPU缓存的方式。通常,对于执行许多写操作的情况,可变数据结构的局部性更好(除非持久性被深深使用)。

内存使用

不可变的数据结构由于内存使用的本质上的尖峰。每次修改后,执行复制。如果不需要这些副本,垃圾收集器可以在下一次收集期间收集旧的数据。只要未收集旧的,未使用的数据副本,就会导致使用的峰值。在需要持久性的情况下,不存在尖峰。

正如你可能已经注意到的,当持久性被考虑时,不变性变得非常引人注目。

示例:响应DBMon基准

基于我们以前的一系列基准,我们决定更新我们的React DBMon基准以在适当的地方使用Immutable.js。由于DBMon在每次迭代后基本上更新所有数据,因此切换到React + Immutable.js不会获得任何好处:Immutable允许React在状态更改后阻止深度相等性检查; 如果在每次迭代之后所有状态都改变,则不可能获得增益。我们因此修改了我们的示例以随机跳过状态更改:

1
2
3
4
5
6
7
8
9
10
// Skip some updates to test re-render state checks.
var skip = Math.random() >= 0.25;
Object.keys(newData.databases).forEach(function (dbname) {
if (skip) {
return;
}
//(...)
});

之后,我们将保存样本的数据结构从JavaScript数组更改为不可变列表。此列表作为参数传递给要渲染的组件。当React的PureRenderMixin添加到组件类中时,可以进行更有效的比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (!this.state.databases[dbname]) {
this.state.databases[dbname] = {
name: dbname,
samples: Immutable.List()
};
}
this.state.databases[dbname].samples =
this.state.databases[dbname].samples.push({
time: newData.start_at,
queries: sampleInfo.queries
});
if (this.state.databases[dbname].samples.size > 5) {
this.state.databases[dbname].samples =
this.state.databases[dbname].samples.skip(
this.state.databases[dbname].samples.size - 5);
}
1
2
3
4
5
6
7
8
9
10
var Database = React.createClass({
displayName: "Database",
mixins: [React.PureRenderMixin],
render: function render() {
//(...)
}
//(...)
});

这是在这种情况下实现增益所需要的。如果数据被认为不变,则不采取进一步的动作来绘制DOM树的该分支。

正如我们以前的一套基准测试一样,我们使用browser-perf来捕获差异。这是JavaScript代码的总运行时间:

DBMon + Immutable.js JavaScript总运行时间

获取完整的结果。

Aside:Immutable.js at Auth0

在Auth0,我们总是在看新的图书馆。Immutable.js也不例外。Immutable.js已经进入我们的lock-next和lock-passwordless项目(lock-next,我们的下一代锁库,仍然在内部开发)。这两个库都是用React开发的。渲染React组件可以在使用不可变数据时获得很好的提升,因为可以通过优化来检查相等性:当两个对象共享相同的引用并且您确定基础对象是不可变的时,您可以确保其中包含的数据没有改变。由于React根据它们是否已更改重新呈现对象,因此不再需要深度值检查。

一个类似的优化可以在Angular.js应用程序来实现。

你喜欢React和Immutable.js吗?向我们发送简历,并指出我们使用这些技术开发的酷项目。

结论

由于功能编程,不变性和其他相关概念的好处被尝试和测试。使用Clojure,Scala和Haskell开发项目背后的成功故事为这些语言强烈倡导的许多想法带来了更大的思维。不变性是这些概念之一:对分析,持久性,复制和比较具有明显的好处,不可变的数据结构甚至在您的浏览器中找到了进入特定用例的方式。像往常一样,当涉及算法和数据结构时,需要仔细分析每个场景以选择正确的工具。关于性能,内存使用,CPU高速缓存行为和对数据执行的操作类型的注意事项对于确定不可变性是否会对您有利是至关重要的。使用Immutable。

如果这篇文章激发了您对函数式编程和数据结构的兴趣,我不能够强烈推荐Chris Okaki的纯功能数据结构,这是一个介绍功能数据结构如何在幕后工作以及如何有效使用它们的一个很好的介绍。Hack on!

123
tomoat

tomoat

21 日志
1 标签
© 2017 tomoat
由 Hexo 强力驱动
主题 - NexT.Muse