重读犀牛书读书笔记

今天有点事没去公司,在家抽空又读了一下犀牛书。由于我们部分服务基于node开发,有一个APP也采用了hybrid的架构,所以最近js写得还蛮多的,再看犀牛书明显感觉更熟悉了。把一些要点再记录一下:

数据类型

js中的数据有基本类型,包括number,bool,null,undefined;以及引用类型,其实就是对象,包括object,function和数组;还有一个不好分类的String,也可以算在基本类型里,但是它是不可变的

上面是犀牛书里的说法,也不能算错。但是我理解js里,除了基本数据类型,其他的都是对象,无论是function,array,都是对象而已

值传递和址传递

按照犀牛书的说法,基本类型是值传递,对象是址传递

但是我觉得,从某种角度说,对象也可以认为是值传递,比如下面的代码:

var a = {name: "a"};

function change(obj){
    obj = {name: "b"};
}

change(a);
console.log(a.name);// a


在函数里修改了obj,令其指向另一个object,但是原本的变量a是不受影响的。说对象是址传递,指的是如果在函数里修改了对象的属性,则原来的对象的属性也会被修改。从这个角度说,对象确实是址传递的

所有变量,都是某个对象的属性

没有任何孤立的变量(不属于任何对象)。全局作用域的变量,其实是全局对象的属性。函数作用域里的变量,其实是调用对象(激活对象)的属性

作用域

js里只有2种作用域,全局作用域和函数作用域,没有块作用域。下面还会提到,函数作用域是可以嵌套的,最终形成作用域链,scope chain

javascript必定有一个执行环境

如同java跑在jvm里一样,javascript也必定跑在一个执行环境中

目前,常见的执行环境有2种,一种是浏览器环境,以每个html为单元,一个html就是一个执行环境,通过<script>引入的所有js代码,都在同一个执行环境里。当url变化,加载了一个新的html,则旧的执行环境不存在,浏览器会重新创建一个全新的执行环境

另一个是node,node进程也是一个js执行环境

与执行环境相对的,是每个执行环境必定有一个全局对象,这也是javascript特殊的地方。在浏览器环境下,这个全局对象是window,所有可以直接访问的顶层变量和函数,如document,alert等,都是window上的属性和方法;在node环境下,全局对象是global

this是什么

this是执行当前方法的对象,有4种情况,只有最后一种比较特殊,其他3种本质上是一样的

被当做函数执行时,this指向全局对象,因为顶层的函数,是全局对象的方法

被当做方法执行时,this指向方法所属的对象,比如o.m()

被当做构造函数时,this指向刚new出来的对象,因为new做的事,其实是创建一个空object,然后在object上执行构造函数

调用函数的apply和call方法时,this指向传进来的参数

scope chain

作用域链,从数据结构上说,是一个对象的链表,节点只有2种类型,链表的头部一定是全局对象,其余的节点分别是嵌套的调用对象(call object),ECMA正式的叫法是激活对象(activation object)。当代码查找变量时,是顺着作用域链从下往上查找的

对于在顶层环境里执行的代码(不属于任何function),其作用域链上只有唯一对象,就是全局对象

在最外围的function被调用时,js解释器就会为这个函数创建一个调用对象,并把它放在scope chain的最顶端。调用对象中包括function里的局部变量,参数,以及arguments变量等。这时scope chain上就有2个对象,先是这个调用对象,然后是全局对象

嵌套的function被调用时,也和上面一样,得到一个调用对象。这时scope chain上有3个对象,inner call object -> outter call object -> global object

访问对象属性的2种方式

一种是obj.name,一般来说这种方法就可以满足需求。但是如果name是不确定的,就需要这样obj[name],实现属性的动态访问

关于arguments属性

调用对象上有arguments属性,它是一个特殊的对象,有点像数组,但是不完全是

比如通过arguments[0],可以取到函数的第一个参数。此外arguments还有一个callee属性,它指向当前正在被调用的function。通常情况下,这个属性不是很有用,但是在实现递归函数时,常常需要用到它

词法作用域

js采用的是词法作用域,也就是说一个函数的作用域链,是在定义这个函数时就决定了,而不是到执行时才决定。最简单的例子:

function f(){

    var scope = "local";
    console.log(scope);
}

var scope = "global";

f();// local

但是,虽然函数在定义的时候,作用域链就确定了,可是作用域上的属性,依然是还没有绑定的,作用域是活的:

function funcMaker(x){

    return function(){
        console.log(x);
    }
}

funcMaker(23)();// 23

内部的那个匿名函数,在定义时就确定了scope chain:自己的call object -> funcMaker的call object -> global object。但是funcMaker的call object的x属性此时还是未知的,只有到了运行时,作用域链上的每个属性才正式地绑定

闭包

闭包可能是javascript里最难理解的概念之一,也是初学者最容易一头雾水的特性。记得以前为了弄清楚什么是闭包,我至少看了20篇不同的帖子,各种说法都有。最终我还是接受犀牛书上的说法:

函数是执行逻辑的代码,和执行上下文(scope chain)组成的整体。在计算机科学学科,这种东西称为闭包。js里的所有函数都是闭包,但是仅仅当嵌入的函数被导出给另一个引用时,这样的闭包才比较有趣,才被特别称呼为“闭包”

也就是说,我们写的任何一个function,其实都是闭包。但是只有当嵌在其他函数内部的函数,被导出(通过return的方式,或者赋值给另一个对象,或者放入一个全局的数组等)时,才会具有一种特殊的特性,这时候才被称为一般意义上的“闭包”

比如这个代码:

function outter(){

    var a = "123";
    
    function inner(){
        console.log(a);
    }

    inner();
}

我们看到inner在外围函数里被直接调用。这时它的作用域链上,有outter的调用对象的引用;而outter的调用对象上,也有inner的引用。他们是相互引用的,引用计数都是1。当outter执行完并返回之后,外部已经没有任何inner的引用,也没有outter调用对象的引用,所以它们双双被垃圾回收了。

但是当inner被导出,情况就完全不同了:

var pointer;

function wrapper(){
    
    var a = "123";
    
    return function inner(){
        console.log(a);
    }
}

pointer = wrapper();

inner的scope chain没有变化,还是自己的call object -> wrapper的call object -> global object

但是现在pointer也持有inner的引用,inner又持有wrapper调用对象的引用,所以wrapper的调用对象不再被回收,即使wrapper()已经返回了,它的调用对象依然存在。这样就形成了一般意义上的闭包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值