帝女剑 发表于 2023-3-14 18:26:06

【读书笔记】你不知道的JavaScript(中)

第一部分 类型和语法

第一章 类型

JavaScript 有七种内置类型:
• 空值(null)
• 未定义(undefined)
• 布尔值( boolean)
• 数字(number)
• 字符串(string)
• 对象(object)
• 符号(symbol,ES6 中新增)
typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// ES6中新加入的类型
typeof Symbol() === "symbol"; // true
typeof null === "object"; // true
// 使用复合条件来检测 null 值的类型
var a = null;
(!a && typeof a === "object"); // true
typeof function a(){ /* .. */ } === "function"; // true 是 object 的一个“子类型”
typeof === "object"; // true 也是 object 的一个“子类型”JavaScript 中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。
已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。
直接调用 undefined 的变量不会报错,但是直接调用 undeclared 的会报错,所以判断变量的 typeof 比直接判断变量更安全;如下;
var a;
if (a) {} //这里会报错;
if (typeof a === undefined) {} // 这里不会报错
if (window.a) {} // 这种方式也可以第二章 值

数组

字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。如
var a = [];
a["123"] = 23;
a.length;// 124类数组转换成数组:

[*]Array.prototype.slice.call(arguments);
[*]Array.from(arguements);
字符串

JavaScript 中字符串是不可变的,而数组是可变的。
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数都是在其原始值上进行操作。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
// 字符串反转
var c = a
// 将a的值转换为字符数组
.split( "" )
// 将数组中的字符进行倒转
.reverse()
// 将数组中的字符拼接回字符串
.join( "" );数字

JavaScript 只有一种数值类型:number(数字),包括“整数”和带小数的十进制数。
// tofixed指定小数位数
var a = 1.234
a.toFixed(0);// 1;
a.toFixed(4);// 1.2340
// toPrecision(..) 方法用来指定有效数位的显示位数
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42 .toFixed(3); // "42.000" 注意其中的空格
var onethousand = 1E3; // 即 1 * 10^3
var onemilliononehundredthousand = 1.1E6; // 即 1.1 * 10^6
0xf3; // 243的十六进制
0Xf3; // 同上
0363; // 243的八进制
// 从 ES6 开始,严格模式(strict mode)不再支持 0363 八进制格式(新格式如
//下)。0363 格式在非严格模式(non-strict mode)中仍然受支持,但是考虑到
//将来的兼容性,最好不要再使用(我们现在使用的应该是严格模式)。
0o363; // 243的八进制
0O363; // 同上
0b11110011; // 243的二进制
0B11110011; // 同上
0.1 + 0.2 === 0.3 // false 合适因为在js中,二进制浮点数不是十分精确的
console.log(.1 + .2); // 0.30000000000000004如何解决浮点数不精确的问题:最常见的方法是设置一个误差范围值,通常称为“机器精度”(machine epsilon),对 JavaScript 的数字来说,这个值通常是 2^-52 (2.220446049250313e-16)。从 ES6 开始,该值定义在 Number.EPSILON 中;
// polyfill
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}

function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // falsePS:会有误差的原因是:计算机是通过二进制的方式存储数据的,在相加的时候,是拿二进制去相加,0.1 的二进制是 0.0001100110011001100...(1100 循环),0.2 的二进制是:0.00110011001100...(1100 循环);在 JavaScript 的 Number 实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留 52 位,再加上前面的 1,其实就是保留 53 位有效数字,剩余的需要舍去,遵从“0 舍 1 入”的原则。
JS 中能够被最大呈现的整数为:2^53 - 1,即 9007199254740991,在 ES6 中被定义为 Number.MAX_SAFE_INTEGER。最小整数是 -9007199254740991,在 ES6 中被定义为 Number.MIN_SAFE_INTEGER。如果需要精确呈现,需要将其转成 string;
整数检测:
// es6 是否是整数
Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false
// polyfill
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
// es6 是否是安全的整数
Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true

// 最大安全数为什么不安全?
2**53 //9007199254740992
2**53 + 1 //9007199254740992
2**53 + 2 //9007199254740994
2**53 + 3 //9007199254740996
2**53 + 4 //9007199254740996

// polyfill
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}特殊等式 Object.is
var a = 42;
console.log( void a, a ); // undefined 42简单值通过值复制的方式来赋值 / 传递,包括 null、undefined、字符串、数字、布尔和 ES6 中的 symbol。
复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值 / 传递。
第三章 原生函数

内部属性 []
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false

var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——晕! 只要用数值除以某个变量,这个变量就是NaN
2/{};
isNaN({})// true;

// es6有Number.isNaN
// polyfill
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!

// 还有个更为简单的方法 即利用 NaN 不等于自身这个特点
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}封装对象包装:  由于基本类型值没有 .length 和 .toString() 这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值包装(box 或者 wrap)一个封装对象;
拆封:得到封装对象中的基本类型值,可以使用 valueOf() 函数;
构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的。
稀疏数组:将包含至少一个“空单元”的数组;
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
a = Number.MAX_VALUE;
// 就近取整模式
console.log(a); // 1.7976931348623157e+308
console.log(a * 2); // Infinity
console.log(a + Math.pow( 2, 969 )); // 1.7976931348623157e+308
console.log(Infinity / Infinity); // NaN
1/Infinity // 0
1/-Infinity //-0
Infinity / 1 //Infinity
-Infinity / 1 //-Infinity永远不要创建和使用空单元数组
除非万不得已,否则尽量不要使用 Object(..)/Function(..)/RegExp(..)
RegExp(..) 有时还是很有用的,比如动态定义正则表达式时
Date(..) 和 Error(..)
// 加法和减法运算不会得到负零(negative zero)
var a = 0 / -3;
// 至少在某些浏览器的控制台中显示是正确的
console.log(a); // -0
// 但是规范定义的返回结果是这样!
console.log(a.toString()); // "0"
console.log(a + ""); // "0"
console.log(String( a )); // "0"
// JSON也如此,很奇怪
console.log(JSON.stringify( a )); // "0"
console.log(+"-0"); // -0
console.log(Number( "-0") ); // -0
console.log(JSON.parse( "-0" )); // -0 JSON.stringify(-0) 返回 "0",而 JSON.parse("-0") 返回 -0。
var a = 0;
var b = 0 / -3;
console.log(a == b); // true
console.log(-0 == 0); // true
console.log(a === b); // true
console.log(-0 === 0); // true
console.log(0 > -0); // false
console.log(a > b); // false

// 是否是-0
function isNegZero(n) {
    n = Number( n );
    return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false如果调用 Date() 时不带 new 关键字,则会得到当前日期的字符串值。其具体格式规范没有规定,浏览器使用 "Fri Jul 18 2014 00:31:02 GMT-0500 (CDT)"这样的格式来显示。
String#indexOf(..):在字符串中找到指定子字符串的位置。
String#charAt(..):获得字符串指定位置上的字符。
String#substr(..)、String#substring(..) 和 String#slice(..) :获得字符串的指定部分。
String#toUpperCase() 和 String#toLowerCase():将字符串转换为大写或小写。
String#trim():去掉字符串前后的空格,返回新的字符串。
以上方法并不改变原字符串的值,而是返回一个新字符串。
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
// polyfill
if (!Object.is) {
    Object.is = function(v1, v2) {
      // 判断是否是-0
      if (v1 === 0 && v2 === 0) {
            return 1 / v1 === 1 / v2;
      }
      // 判断是否是NaN
      if (v1 !== v1) {
            return v2 !== v2;
      }
      // 其他情况
      return v1 === v2;
    };
}第四章 强制类型转换

类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。
抽象值操作

toString


[*]该方法可重新定义;
[*]JSON.stringfy 在将 JSON 对象序列化为字符串时也用到了 ToString 在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在数组中则会返回 null(以保证单元位置不变)。
JSON.stringify(value[, replacer[, space]])

[*]

[*]value: 必需, 要转换的 JavaScript 值(通常为对象或数组)。
[*]replacer: 可选。用于转换结果的函数或数组。
如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""。
如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。
[*]space: 可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。

(1) 字符串、数字、布尔值和 null 的 JSON.stringify(..) 规则与 ToString 基本相同。
(2) 如果传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方法,那么该方法会在字符串化前调用,以便将对象转换为安全的 JSON 值。
JSON.stringify(..) 并不是强制类型转换。在这里介绍是因为它涉及 ToString 强制类型转换
Object.prototype.toString.call( );
// ""
Object.prototype.toString.call( /regex-literal/i );
// ""
Object.prototype.toString.call( null );
// ""
Object.prototype.toString.call( undefined );
// ""
Object.prototype.toString.call( "abc" );
// ""
Object.prototype.toString.call( 42 );
// ""
Object.prototype.toString.call( true );
// ""ToNumber

true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive(参见 ES5 规范 9.1 节)会首先检查该值是否有 valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
a.join( "-" ); // "--"
b.join( "-" ); // "--"
a.map(function(v,i){ return i; }); // [ undefined x 3 ]
b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
var a = Array.apply( null, { length: 3 } );// 等同于Array(undefined, undefined, undefined)
a; // [ undefined, undefined, undefined ]ToBoolean

假值:
• undefined
• null
• false
• +0、-0 和 NaN
• ""
假值列表以外的值都是真值。
假值对象(falsy object)
document.all 浏览器自带的来判断浏览器是否是老版本的 IE。
:if(document.all) { /_ it’s IE _/ }
// Date.now的polyfill
if (!Date.now) {
Date.now = function(){
return (new Date()).getTime();
};
}显式强制类型转换
var a = 'abcde';
a.charAt(1) // 'b'
a.charCodeAt(1) //98
a.concat(1) //'abcde1'
a.endsWith('e') // true
a.indexOf('b') //1
a.includes('b') //true
a.lastIndexOf('d') //3
a.match(/e/g) //['e']
a.repeat()//''
a.repeat(2)//'abcdeabcde'
a.replace('d', 4)//'abc4e'
a.replaceAll('e', '5')//'abcd5'
a.search(/b/)//1
a.slice(0, 1)//'a'
a.split('')//(5) ['a', 'b', 'c', 'd', 'e']
a.startsWith(1)//false
a.substr(0,2) // 'ab' 第一个参数 起始下标 第二参数 长度
a.substring(1,2)//'b' 第一个参数 起始下标 第二个参数 结束下标
'B'.toLowerCase()//'b'
a.toUpperCase()//'ABCDE'
' fd '.trim() //'fd'一元运算符 - 和 + 一样,并且它还会反转数字的符号位。由于 -- 会被当作递减运算符来处理,所以我们不能使用 -- 来撤销反转,而应该像 - -"3.14" 这样,在中间加一个空格,才能得到正确结果 3.14。
JSON.stringify( undefined ); // undefined
JSON.stringify( function(){} ); // undefined
JSON.stringify(

); // ""
JSON.stringify(
{ a:2, b:function(){} }
); // "{"a":2}"

// 对包含循环引用的对象执行 JSON.stringify(..) 会出错。
// Uncaught TypeError: Converting circular structure to JSON
//   --> starting at object with constructor 'Object'
//   |   property 'c' -> object with constructor 'Object'
var o = { };
var a = {
b: 42,
c: o,
d: function(){}
};
// 在a中创建一个循环引用
o.e = a;
// 循环引用在这里会产生错误
// JSON.stringify( a );
// 自定义的JSON序列化 toJSON() 应该“返回一个能够被字符串化的安全的 JSON 值”,而不是“返回一个 JSON 字符串”。
a.toJSON = function() {
// 序列化仅包含b
return { b: this.b };
};
JSON.stringify( a ); // "{"b":42}"
var a = {
b: 42,
c: "42",
d:
};
JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
JSON.stringify( a, function(k,v){
if (k !== "c") return v;
} );
// "{"b":42,"d":}"
var a = {
b: 42,
c: "42",
d:
};
JSON.stringify( a, null, 3 );
// "{
//                 "b": 42,
//                 "c": "42",
//                 "d": [
//                                 1,
//                                 2,
//                                 3
//                 ]
// }"
JSON.stringify( a, null, "-----" );
// "{
// -----"b": 42,
// -----"c": "42",
// -----"d": [
// ----------1,
// ----------2,
// ----------3
// -----]
// }"JavaScript 有一处奇特的语法,即构造函数没有参数时可以不用带 ()。于是我们可能会碰到 var timestamp = +new Date; 这样的写法。这样能否提高代码可读性还存在争议,因为这仅用于 new fn(),对一般的函数调用 fn() 并不适用。
奇特的 ~ 运算符
~x 大致等同于 -(x+1)。
用法
var a = {
valueOf: function(){
return "42";
}
};
var b = {
toString: function(){
return "42";
}
};
var c = ;
c.toString = function(){
return this.join( "" ); // "42"
};
Number( a ); // 42
Number( b ); // 42
Number( c ); // 42
Number( "" ); // 0
Number( [] ); // 0
Number( [ "abc" ] ); // NaN字位截除
~~x 能将值截除为一个 32 位整数,x | 0 也可以,而且看起来还更简洁。
var a = "false";
var b = "0";
var c = "''";
var d = Boolean( a && b && c );// "''"是真值显式解析数字字符串
解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回 NaN。解析字符串中的浮点数可以使用 parseFloat(..) 函数;
var a = 42;
var b = String( a );// String(..) 遵循前面讲过的 ToString 规则
// var b = a.toString(); 同上
var c = "3.14";
var d = Number( c ); // Number(..) 遵循前面讲过的 ToNumber 规则
// var d = +c; 同上   +是运算符的一元(unary)形式
b; // "42"
d; // 3.14
var c = "3.14";
var d = 5+ +c;
d; // 8.14ES5 之前的 parseInt(..) 有一个坑导致了很多 bug。即如果没有第二个参数来指定转换的基数(又称为 radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数。从 ES5 开始 parseInt(..) 默认转换为十进制数,除非另外指定。如果你的代码需要在 ES5 之前的环境运行,请记得将第二个参数设置为 10。
解析非字符串
1 + - + + + - + 1; // 2 负负得正
+new Date();// 日期显式转换为数字
Date.now();// 比较好的写法
// Date.now的polyfill 不建议对日期类型使用强制类型转换,应该使用 Date.now() 来获得当前的时间戳,使用 new Date(..).getTime() 来获得指定时间的时间戳。
if (!Date.now) {
Date.now = function() {
return +new Date();
};
}parseInt(1/0, 19) 实际上是 parseInt("Infinity", 19)。第一个字符是 "I",以 19 为基数时值为 18。第二个字符 "n" 不是一个有效的数字字符,解析到此为止,和 "42px" 中的 "p"一样
显式转换为布尔值: 使用 Boolean(a) 和 !!a 来进行显式强制类型转换
隐式强制类型转换: 代码可读性不好,但是也是可以减少冗余,让代码更简洁 抽象和隐藏那些细枝末节,有助于提高代码的可读性
字符串和数字之间的隐式强制类型转换
if (!~a.indexOf( "ol" )) { // true 这里~ 比 >= 0 和 == -1 更简洁。
// 没有找到匹配!
}
~12.1 // -13
-(12.1+1) // -13.1
~~12.1 // 12a + "" 会对 a 调用 valueOf() 方法,然后通过 ToString 抽象操作将返回值转换为字符串。
Math.floor( -49.6 ); // -50
~~-49.6; // -49
~~1E20 / 10; // 166199296
1E20 | 0 / 10; // 1661992960
(1E20 | 0) / 10; // 166199296布尔值到数字的隐式强制类型转换
如果其中有且仅有一个参数为 true,则 onlyOne(..) 返回 true。
var a = "42";
var b = "42px";
Number( a ); // 42
parseInt( a ); // 42
Number( b ); // NaN
parseInt( b ); // 42|| 和 &&:选择器运算符”(selector operators)或者“操作数选择器运算符”(operand selector operators)
在 a ? a : b 中,如果 a 是一个复杂一些的表达式(比如有副作用的函数调用等),它有可能被执行两次(如果第一次结果为真)。而在 a || b 中 a 只执行一次,其结果用于条件判断和返回结果(如果适用的话)。
Symbol 符号的强制类型转换
parseInt( 1/0, 19 ); // 18
parseInt( 0.000008 ); // 0 ("0" 来自于 "0.000008")
parseInt( 0.0000008 ); // 8 ("8" 来自于 "8e-7")
parseInt( false, 16 ); // 250 ("fa" 来自于 "false")
parseInt( parseInt, 16 ); // 15 ("f" 来自于 "function..")
parseInt( "0x10" ); // 16
parseInt( "103", 2 ); // 2 3在二进制中不存在,所以取10 转10进制 为2== 允许在相等比较中进行强制类型转换,而 === 不允许
== 和 === 都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。
抽象相等:==
字符串和数字之间的相等比较:
(1) 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
(2) 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
其他类型和布尔类型之间的相等比较:
(1) 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
(2) 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
null 和 undefined 之间的相等比较:
(1) 如果 x 为 null,y 为 undefined,则结果为 true。
(2) 如果 x 为 undefined,y 为 null,则结果为 true。
var a = ;
var b = ;
a + b; // "1,23,4" 数组的valueOf() 操作无法得到简单基本类型值,于是它转而调用 toString()a == null 等价为 a=null&& a=undefined
对象和非对象之间的相等比较:
(1) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
(2) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。
var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"因为没有对应的封装对象,所以 null 和 undefined 不能够被封装(boxed),Object(null)和 Object() 均返回一个常规对象。NaN 能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回 false,因为 NaN 不等于 NaN
function onlyOne() {
   var sum = 0;
   for (var i=0; i < arguments.length; i++) {
   // 跳过假值,和处理0一样,但是避免了NaN
   if (arguments) {
                   sum += arguments;
   }
   }
   return sum == 1;
}
var a = true;
var b = false;
onlyOne( b, a ); // true
onlyOne( b, a, b, b, b ); // true安全运用隐式强制类型转换
如果两边的值中有 true 或者 false,千万不要使用 ==。
如果两边的值中有 []、"" 或者 0,尽量不要使用 ==。
抽象关系比较

比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较。
var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError4.6 生成器并发

通信顺序进程(Communicating Sequential Processes,CSP): 讲道理 这里没看懂 跳过吧
4.7 形式转换程序

形实转换程序(thunk):JavaScript 中的 thunk 是指一个用于调用另外一个函数的函数,没有任何参数
var a = 42;
var b = "42";
a === b; // false
a == b; // true
var x = true;
var y = "42";
x == y; // false
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false第 5 章 程序性能

5.1 Web Worker

Worker 之间以及它们和主程序之间,不会共享任何作用域或资源,那会把所有多线程编程 的噩梦带到前端领域,而是通过一个基本的事件消息机制相互联系。
Web Worker 通常应用于哪些方面呢?

[*]处理密集型数学计算
[*]大数据集排序
[*]数据处理(压缩、音频分析、图像处理等)
高流量网络通信
使用 worker 的应用需要在线程之间通过事件机制传递大量的信息,可能是双向的。
在早期的 Worker 中,唯一的选择就是把所有数据序列化到一个字符串值中。除了双向序 列化导致的速度损失之外,另一个主要的负面因素是数据需要被复制,这意味着两倍的内 存使用(及其引起的垃圾收集方面的波动)。
如果要传递一个对象,可以使用结构化克隆算法,这样就不用付出 to-string 和 from-string 的性能损失了,但是这种方案还是要使用双倍的内存。IE10 及更高版本以及所有其他主流浏览器都支持这种方案。
还有一个更好的选择,特别是对于大数据集而言,就是使用 Transferable 对象
这时发生的是对象所 有权的转移,数据本身并没有移动。一旦你把对象传递到一个 Worker 中,在原来的位置 上,它就变为空的或者是不可访问的,这样就消除了多线程编程作用域共享带来的混乱。 当然,所有权传递是可以双向进行的。
共享 Worker: 整个站点或 app 的所有页面实例都可以共享的中心 Worker;
如果有某个端口连接终止而其他端口连接仍然活跃,那么共享 Worker 不会 终止。而对专用 Worker 来说,只要到实例化它的程序的连接终止,它就会 终止。
第 6 章 性能测试和调优

6.1 性能测试

用重复运行,然后取平均值去测试性能误差会比较大,应该基于统计学去实践,比如 Benchmark.js 库;
6.2 环境为王

测试不真实的代码只能得出不真实的结论。如果有实际可能的话,你 应该测试实际的而非无关紧要的代码,测试条件与你期望的真实情况越接近越好。只有这 样得出的结果才有可能接近事实。
6.3 jsPerf.com

如果你想要得到可靠的测试结论的话,就需要在很多不同的环境(桌面浏览 器、移动设备,等等)中测试汇集测试结果。
6.4 写好测试用例

写有意义的测试用例;
6.5 微性能

只考虑微性能是不合理的,可读性,包括一些浏览器不同处理问题也要考虑;
6.6 尾调用优化

a
尾调用:一个出现在另一个函数“结尾”处的函数调用;
调用一个新的函数需要额外的一块预留内存来管理调用 栈,称为栈;
尾调用优化(TCO):不需要创建一个新的栈帧,而是可以重用已 有的 bar(..) 的栈帧。这样不仅速度更快,也更节省内存
TCO 允许一个函数在结尾处调用另外一个函数来执行,不需要任何额外资源。这意味着,对递归算法来说,引擎不再需要限制栈深度。

来源:https://www.cnblogs.com/NSGUF/p/17213579.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 【读书笔记】你不知道的JavaScript(中)