技术书目笔记

Posted by wantu on March 9, 2019

开这个的原因

  温故而知新。内容控制在原先读过的技术书目上。

JS高级程序设计

变量:
5中基本类型:Null、Undefined、String、Number、Boolean。注意是类型(首字母大写)
其中注意:typeof操作符 对 null的结果:[typeof null ==object] [typeof 函数== function] [typeof NaN ==> NaN]

对变量进行声明原则是不需要进行初始化的,因为该变量会自动的被赋予undefined。但是显式的初始化仍然是明智的。 因为它有助于我们辨别一个变量到底是没初始化还是没有声明。

1
2
3
4
5
//比如a是一个没有声明的变量
//那么typeof a ==>undefined
//如果a是有声明的而且也进行了显式的初始化
let a = 0
typeof a === 'number' //true

逻辑上讲:null值表示空对象指针。这也是typeof null == object的原因。 如果一个变量将来打算用来保存对象的,那么务必将其初始化为null。

Boolean
5个false的情况:null,undefined,”“,0,false

Number
浮点数值进行计算精度丢失问题。0.1+0.2==>0.300000000000001
.1(有效,等同0.1)
较大较小值可以使用科学计数法进行表示。1.22e5 === 122000

数值范围:Number.MIN_VALUE~Number.MAX_VALUE 大多数浏览器的具体范围是:5e-324~1.797e308.超过范围数值将变成Infinity和-Infinity。可使用isFinite()函数判断数值是否在范围内。false为不在范围内。 一旦为数值变成无穷值那么将不在参与下一次的计算。
NaN(Not a Number)非数值。用来表示本来要返回数值的操作数未返回数值的情况。任何数/0 ==>NaN
2个特点:任何涉及NaN的操作都将返回NaN.其次NaN跟任何数值都不相等,包括NaN自己。

3个数值转换函数:Number(),parseInt(),parseFloat().
注意:Number(undefined) === NaN Number(‘’) ===0,Number(null)===0,或者转换字符串不满足数值格式转换结果也是NaN。
如果是转换的是一个对象,则调用对象的valueOf方法,然后依照规则进行转换,如果转换的结果是NaN,则调用toString()方法。然后再依照那个规则进行转换。不建议使用Number()进行字符串的转换。[先是调用valueOf()不假但是toString()好像有问题]
使用parseInt()。转换规则:1、忽略前面的空格2、第一个字符,第一个字符不是数字返回NaN.如果是继续查找直至全部字符查找完毕或者遇到非数字字符,返回刚刚检索过的数字字符,并以数值的形式返回。
parseInt(‘’) ==>NaN parseInt(‘123.4’) ==> 123
如果第一个字符是数字字符,parseInt()也能识别出各种整数的格式。(8进制【es5之后丧失】、16进制数)
parseInt(010) ==> 10 (es5之后parseInt方法不在具有解析8进制数的能力,前导0会无效) parseInt(0x1) ==> 16(16进制数) 困惑为了消除parseInt()函数上的困惑,该函数提供了第二个参数:转换时使用的基数(多少进制)。
parseFloat()函数跟parseInt()函数类似。2点区别:1、parseFloat会始终忽略前导0。2、解析遇到第一个小数点有效遇到第二个小数点无效

String:
字符串不可变。要改变某个字符串就得删除原先得字符串。
转换为字符串:1、toString()方法。null和undefined没有此方法。
数值的toString()可以传递一个基数,用以转换成不同格式。null和undefined没有toString()方法,强项调用该方法会返回这两个值的字面量。

操作符
比较操作符:
数值和数值 比大小
数值和 数值型字符比较,将数值型字符转为数值 再比较两者的大小
数值型字符和 数值型字符比较,比较字符编码大小。“23” < “5” 因为:2–>字符编码为50 3–>字符编码为51
字符和字符 比较 也是比较字符编码大小
NaN和任何操作数比较都返回false

相等操操作符:
== 和 != 两个操作符都会强制转换操作数,然后比较他们的相等性。
如果操作数中有一个是布尔类型,那么将其转为数值,true转为1false转为0,而后进行比较
如果有一个操作数是对象,另外一个不是那么调用这个对象的valueOf方法得到基本的数值类型再根据之前的比较规则进行比较。
null和undefined是相等的
如果有一个操作数是NaN那么相等操作符都将返回false.
两个对象是一个对象相等操作符才返回true
===全相等和!==不全等,不会对操作数进行转换其余同相等和不等

逗号操作符:
用于声明:一条语句可以执行多个声明。let a = 3,b = 2,c = 4;
用于赋值:let a = (1,2,3,4,5);a === 5。因为5是最后一项。此用法不常见。

break和continue中的一种:
outermost标签表示外部的for循环。
内部循环中 的 break 语句带了一个参数:要返回到的标签。添加这个标签的结果将导致 break 语句不仅会退出内 部的 for 语句(即使用变量 j 的循环),而且也会退出外部的 for 语句(即使用变量 i 的循环)。

1
2
3
4
5
6
7
8
9
10
11
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
     for (var j=0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outermost;
        }
	num++; 
     }
}
alert(num);    //55

continue同样也可以和标签一起连用。注:continue可以在while中使用。
这种情况下,continue 语句会强制继续执行循环——退出内部循环,执行外部循环。当 j 是 5 时,continue 语句执行,而这也就意味着内部循环少执行了 5 次,因此 num 的结果是 95。

1
2
3
4
5
6
7
8
9
10
11
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
        for (var j=0; j < 10; j++) { 
            if (i == 5 && j == 5) {
                continue outermost;
        }
	num++; 
    }
}
alert(num);    //95

使用label标签语句,一定要使用描述性的标签,同时不要使用过多的标签。

函数:
return语句也可以不带任何的返回值。函数在停止执行时将返回undefined值。
js没有重载。同名函数函数名只属于后面那个函数。

基本类型按值访问、引用类型按引用访问。

局部变量会在函数执行完立即销毁(实际gc回收不是立即的)

执行环境与作用域
执行环境(简称环境)定义了变量或函数有权访问的其他数据,决定了他们各自的行为。
每个执行环境都有一个 与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们 编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。因 此所有全局变量和函数都是作为 window 对象的属性和方法创建的。某个执行环境中的所有代码执行完 毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退 出——例如关闭网页或浏览器——时才会被销毁)。

延长作用域链:
try-catch语句的catch块/with语句。这两个语句都可以在作用域链的前端加上1个变量对象。
对 with 语句来说,会将指定的对象添加到 作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
查询标识符,通过搜索来确认这个标识符实际代表什么。整个搜索过程将一直回溯到全局环境的变量对象。

垃圾回收
局部变量只在函数执行过程中存在。具体到浏览器垃圾回收主要有两个策略:
标记清除和引用计数
引用计数循环引用问题:
循环引用指的是对象A中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的 引用. IE 中有一部分对象并不是原生 JavaScript 对象。例如,其 BOM 和 DOM 中的对象就是 使用 C++以 COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM 对象的垃圾 收集机制采用的就是引用计数策略。因此,即使 IE 的 JavaScript 引擎是使用标记清除策略来实现的,但 JavaScript 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会 存在循环引用的问题。下面这个简单的例子,展示了使用 COM 对象导致的循环引用问题:

1
2
3
4
var element = document.getElementById("some_element"); 
var myObject = new Object(); 
myObject.element = element;
element.someObject = myObject;

这个例子在一个 DOM 元素(element)与一个原生 JavaScript 对象(myObject)之间创建了循环 引用。其中,变量 myObject 有一个名为 element 的属性指向 element 对象;而变量 element 也有 一个属性名叫 someObject 回指 myObject。由于存在这个循环引用,即使将例子中的 DOM 从页面中 移除,它也永远不会被回收。

为了避免类似这样的循环引用问题,最好是在不使用它们的时候手工断开原生 JavaScript 对象与 DOM 元素之间的连接。例如,可以使用下面的代码消除前面例子创建的循环引用:

1
2
myObject.element = null; 
element.someObject = null;

将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就 会删除这些值并回收它们占用的内存。 为了解决上述问题,IE9 把 BOM 和 DOM 对象都转换成了真正的 JavaScript 对象。这样,就避免了 两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象。
优化内存占用的最佳方式,就是为执行 中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个 做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。解除引用不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便于gc下次运行时将其回收

引用类型:
对象类型:
除非必须使用变量来访问属性,否则我们建议使用点表示法。
Array类型:
Array的length属性很有特点,它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。
转换方法:
所有对象都具有 toLocaleString()、toString()和 valueOf()方法。
使用 join()方法,可以使用不同的分隔符来构建这个字符串。
栈方法:
push()入栈,pop()出栈。
队列方法:
push()入队列、shift()取出队列的第一项
unshift() 和 pop()从反方向来模拟队列,即从数组前端添加项从数组末端移除项。
重排序方法:
reverse():反转数组 sort()
迭代方法:
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。
filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。

forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。 以上方法都不会修改数组中的包含的值。
归并方法:
reduce()和 reduceRight()这两个方法都会迭 代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历 到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。

你不知道的js(上)

编译
程序中的一段代码在执行之前会经历三个步骤,这三个步骤统称为编译:
1、分词/词法分析
2、解析/语法分析
3、代码生成
js代码的编译与其他大部分语言编译是不同的,因为js代码的编译不是发生在构建之前的。
大部分情况下js代码的编译发生在代码执行的前几微秒的时间内。

作用域、引擎、编译器
引擎负责整个js程序的编译以及执行过程
编译器负责语法分析以及代码生成
作用域负责收集并维护所有声明的标识符组成的一系列查询并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

理解var a = 2
1、编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果存在则忽略这个声明,如果否,则会在这个作用域的集合中哦声明一个新的变量,并命名为a.
2、编译器会为引擎生成运行所需要的代码,这些代码会被用来处理 a=2 这个赋值操作。引擎运行时会访问作用域,在当前作用域是否存在一个a变量,如果存在引擎会使用这个变量,如果否,继续查找该变量。

LHS/RHS
当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。

1
2
a = 2;//LHS
a;//RHS

作用域嵌套

当一个块或者函数嵌套在另一个块或者函数中的时候会发生作用域的嵌套,因此在当前作用域无法找到某个变量的时候,引擎会向上层嵌套的作用域继续查找,直到找到该变量或抵达最外层的作用域为止。
值得注意的是:作用域查找会在找到第一个匹配的标识符停止。

词法作用域
定义在词法阶段的作用域。词法作用域是由人决定的,我们在代码中将某个变量或者块写在哪来决定。大部分情况,词法分析器在处理代码时会保持词法作用域不变。(欺骗词法将会导致性能下降)

两中欺骗词法的机制:
1、eval()函数
该函数接受一个字符串作为参数,并将其内容视为好像在书写时就已经存在于程序中的这个位置的代码。默认情况下,如果eval函数所执行的代码包含一个或者多个声明,就会对它所处的词法作用域进行修改(严格模式下eval()函数有其自己的词法作用域,意味者其中的声明无法修改所在的词法作用域)
2、with关键字
它实际上是根据你传递给他的对象凭空创建一个全新的词法作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var o1 = {
    a:3
};
var o2 = {
    b:2
};

function foo(obj){
    with(obj){
        a=2;
    }
}

foo(o1);
foo(o2);

console.info(o1.a);//undefined
console.info(a);//3 a被泄漏到全局作用域上了

如何泄漏的:o2,foo()的作用域和全局作用域都没有找到a这个标识符,因此当a = 2执行时自动创建一个全局变量(非严格模式下)。

函数作用域
指属于这个函数的全部变量都可以在整个函数范围内使用以及复用。
在任意代码段外面添加包装函数,可以将内部变量和函数定义隐藏起来,外部无法访问内部的任何内容。但是引入了一个额外的问题。定义这个函数,函数名污染了所在的作用域,其次必须显示的调用这个函数才可以运行其中的代码。
解决方法:

1
2
3
4
5
6
var a = 2;
(function foo(){
var a = 3;
    console.info(a);
})();
console.info(a);//2

这样一改的话,函数会被当作函数表达式而不是函数声明来处理。
区分函数声明于函数表达式:
方法:看function出现的位置,如果function是第一个词,那么这就是一个函数的声明。

匿名和具名:
函数表达式可以是匿名的,但是函数声明是不可以忽略函数名的。在js中函数声明匿名好似非法的。
匿名的缺点:
1、栈追踪中不会显示有意义的函数名,使得调试变得困难。
2、如果没有函数名,函数引用自身只能通过过期的argument.callee
3、匿名函数省略了代码对代码可读性/可理解性很重要的函数名。

IIFE
代表的是立即执行的函数表达式。上面的代码是一种写法。还有一种。(function foo(){…}())
两种在功能上是一样的,全凭个人喜好选择。

块级作用域
1、with关键字,with从对象船舰出的作用域仅在with声明中而非外部作用域中有效。
2、try/catch。es3的时候就规定了try/catch中的catch分句会创建一个块级作用域,其声明的变量仅在catch中有效。

尽可能用let关键字替代var
let关键字可以将变量绑定到所在的任意作用域中(通常是{…}内部)。换句话说let为其声明的变量隐式的劫持了所在的块级作用域。

提升
只有声明才会被提升,而赋值或者其他的运行逻辑会留在原地。即便是具名函数表达式,名称标识符在赋值之前也是无法在所在作用域中使用。

1
2
3
4
5
var foo;
foo();//TypeError
foo = function foo(){
...
}

函数优先:多个重复声明的标识符,函数会首先被提升。
闭包:
闭包会组织gc对某个函数执行完的内部作用域进行回收。闭包使得函数在别处被调用的都可以继续访问定义时的词法作用域。 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。

模块模式:
1、必须要有外部封闭函数。
2、封闭函数必须至少返回一个内部函数,这样内部函数才能在私有作用域中形成闭包。
现代模块机制:本质上都是将这种定义模块的定义封装到一个友好的API中。

动态作用域:
js中的作用域就是词法作用域。js并不具备动态作用域。
动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心他们在什么地方调用。作用域链是基于调用栈的,而不是代码中的作用域嵌套。

块级作用域的替代
1、使用try{}catch(){}.es3开始有的。性能十分糟糕。

this词法
胖箭头函数的好处:
1、可以让你少敲几个单词。
2、箭头函数在涉及this绑定的行为和普通函数的行为完全不一致。它放弃所有普通this绑定的规则,取而代之的是用当前的词法作用域覆盖了this本来的值。
丢失this绑定:

1
2
3
4
5
6
7
8
9
10
var obj = {
id:'awesome',
    cool:function coolFn(){
        console.info(this.id);
    }
}

var id = 'not awesome';
obj.cool();//awesome;
setTimeOut(obj.cool(),100);//nowt awesome

第二个输出是出乎预期的,因为cool函数丢失了同this的绑定。