最近两本书 读书笔记(1)

前言

前段时间买了些书,然后这几天抽空在看,《javascript启示录》125页,看上去很小巧的一本书,读起来也非常轻松,还有一本《你不知道的javascript》,这本书里的内容很不错,并且结合了es6的内容。

我一向是支持看书做笔记,不管是应用在项目里,还是写读书笔记,总觉得写下来的加深印象。毕竟好记性不如烂笔头,尤其是键盘输入,给做笔记节省了不少功夫。

书已经更新到2015年阅读书单。

javascript启示录

这本书对初学者很有爱的,基本帮你理清楚了一些未来可能遇到坑和一些概念。我将记录我自己觉得有帮助的内容。

首先,是关于复制。

var a = 3;
var b = a;

b = 4;
console.log(a); //3
console.log(b); //4
 
var object = {};
var newobject = object;

一个是复制值,一个是复制对象。

对象是通过引用进行存储。复制的是引用,而不是实际的值。当更改对象里的内容时,所有引用相同地址的变量的值都会被修改。

注意:当通过new 关键词创建String(),Number(),Boolean()值时,或者这些值在幕后被转为复杂对象,值依然是按照值进行存储复制的。

如果要真正复制一个对象,必须从旧的对象中提取值,并将提取的值注入新对象。

然后是对象之间的对比。

var oa = {same:"same"};
var ob = {same:"same"};

console.log(oa === ob) //false

两个单独创建的对象,即使具有相同的类型并拥有完全的属性,它们也是不相等的。

并且在此书中收获了一个对我来说很有价值的东东:

var myfunction  = new Function('n1','n2',"return n1 + n2");
console.log(myfunction(2,4));//6

在最后一个参数之前传递给构造函数的任何参数都可用于创建的函数。

而且消化不掉的它会默认省略:

var myfunction  = new Function('n1','n2','n3',"return n1 + n2");
console.log(myfunction(2,3));//5

将构造函数创建的实例链接至构造函数的prototype函数

虽然原型只是一个对象,但它是特殊的,因为原型链将每个实例都链接至其构造函数的prototype属性。这意味用new关键词构造函数创建对象时,他都会在创建的实例和创建对象的构造函数之间的prototype添加一个隐藏的链接。它在实例中被称为_proto_。

构造函数被调用时,javascript在后台将一切链接起来。正是这个链接使得原型链成为一个链。

来看个例子:

Array.prototype.zhaoying = "linshui";
var my = new Array();
console.log(my.__proto__.zhaoying)

记住proto左右是两个下划线。

你不知道的javascript

词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。javascript中有两个机制可以“欺骗”词法作用域:eval()和with。

javascript并没有块作用域的相关功能

来看一个例子:

var foo = true;

if(foo){
  var bar = foo * 2;
  bar = bar + 1;
  console.log(bar);
}

bar变量仅在if声明的上下文中使用,因此如果能将它声明在if块内部中会是一个很有意义的事情,但是,当使用var声明变量时,它写在哪里都一样。它最终都会属于外部作用域。

这段代码只是伪装出形式上的块作用域,如果要使用这种形式,确保没在作用域其他地方意外使用bar只能靠自觉性。(ps:在es6中let已解决。)

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用。)

函数优先

看一下代码:

foo();//1

var foo;

function foo(){
  console.log(1);
}

foo = function(){
  console.log(2);
}

会输出1而不是2 这个代码片段会被引擎这么解析:

function foo(){
  console.log(1);
}

foo();//1

foo = function(){
  console.log(2);
}

var foo尽管出现在function foo()之前,但它是重复的声明(因此被忽略) 函数的声明会被提升到普通变量之前。

尽管重复的var声明会被忽略,但出现在后面的函数声明还是可以覆盖前面的。

foo();//3

function foo(){
  console.log(1);
}

var foo = function(){
  console.log(2);
}

function foo(){
  console.log(3);
}

会输出3,建议自己在chrome中尝试console

这说明同一个作用域中进行重复定义是非常糟糕的。

接下来是老调重弹的重点-闭包


当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

我们来看下经典的例子

for(var i = 1 ; i <= 5;i++){

    setTimeout(function timer(){
      console.log(i);
    },i*1000);

}

对于这段代码,预期分别输出1-5.每秒一次

但事实上,结果是每秒一次的频率输出5个6。这是被很多闭包讲解中所举的例子。

我们先看6从哪里来,这个循环的终止条件不再是<=5 条件成立时,i的值是6.因此输出显示的循环结束时i的最终值。

延迟函数的回调会在循环结束时执行。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(..,0)所有的回调依旧在循环结束后执行。

代码中有什么缺陷导致它的行为同语义所暗示的不一致?

缺陷是我们试图假设循环中的每一个迭代在运行时都会给自己'捕获'一个i的副本,但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

所有函数共享一个i的引用,循环结构让我们误以为背后还有更发杂的机制在起作用。实际上没有。如果将延迟函数的回调重复定义5次,完全不用循环。那么它同这段代码是完全等价的。

于是我们想到用更多闭包作用域去解决这个问题。如下:

for(var i = 1 ; i <= 5;i++){
  (function(){
    setTimeout(function timer(){
      console.log(i);
    },i*1000);
  })();
}

然而还是不行,如果作用域是空的,那么仅仅将它们封闭是不够的。仔细看一下,我们的IIFE只是一个什么都没有的空作用域。

我们只需要加点东西就能让它为我们所用:

for(var i = 1 ; i <= 5;i++){
  (function(){
    var j = i;
    setTimeout(function timer(){
      console.log(j);
    },j*1000);
  })();
}

今天先到这里。

Table of Contents