Index

no pain no gain

早上起床神经质了一把

忽然意识到

对待工作应该和对待女朋友是一样的

短期热血的稀罕只能带我来到山前,持久的热爱和专一,才能帮助向上攀爬,没有痛苦,就没有成长

只是我在从实习到现在花了2年半的时间才意识到

“JS高程”读书笔记1

String

用String()来确定某一变量是null或undefined

var vUndef, vNull = null;

String(vUndef);     // "undefined"
String(vNull);      // "null"

String(vUndeclare); // ReferenceError: vUndeclare is not defined

ES5中这样描述:

The String Constructor Called as a Function

When String is called as a function rather than as a constructor, it performs a type conversion.

String ( [ value ] )

Returns a String value (not a String object) computed by ToString(value). If value is not supplied, the empty String "" is returned.

直接调用String()作为方法时,将会执行类型转换,返回经过ToString(value)得到的字符串字面量(与new String()不同),或者空字符串('').

ToString转换

非Number类型调用时:

  • Undefined -- "undefined"
  • Null -- "null"
  • Boolean
    • true -> "true"
    • false -> "false"
  • String -- 原样返回,不进行转换
  • Object
    1. 调用ToPrimitive(input argument, hint String)得到字符串类型的primValue
    2. 返回ToString(primValue)

Number调用时:

这里假如有数字m:

  1. NaN -- "NaN"
  2. m为+0或-0 -- "0"
  3. m<0 -- 拼接的"-"与ToString(m)
  4. m为无穷大或小 -- 返回"Infinity"
  5. 否则会有一坨关于计算数字字符串表现形势下小数点和前导0等等相关的算法和规则ToString Applied to the Number Type

Object

Object的实例拥有以下方法:

  • constructor -- 创建当前对象的构造函数
  • hasOwnProperty(propertyName) -- 检查给定的属性在当前实例中(而不是在实例的原型中)是否存在,其中属性名(propertyName)必须为字符串
  • isPrototypeOf(object) -- 判断要检查其原型链的对象是否存在于指定对象实例中,是则返回true,否则返回false(A.prototype.isPrototypeOf(a))
  • propertyIsEnumerable(propertyName) -- 检查指定的属性名是否是自身的可枚举属性,只有同时满足以下三点才返回true,参考MDN讲解
    • property是该实例的属性
    • property非继承来的属性
    • 可以通过for...in语句循环枚举
  • toLocaleString() -- 返回根据所在地的语言习惯得到的字符串
  • toString() -- 返回对象的字符串表示
  • valueOf() -- 返回对象最有意义的原始值,以字符串、数字、或布尔值表示

操作符

一元操作符

使用前置递增或递减操作符,更改被操作对象的值

var a0 = 29;
var b0 = --a0 + 2;

a0;     // 28
b0;     // 30


var a1 = 29;
var b1 = a1-- + 2;

a1;     // 28
b1;     // 31

位操作符

将字符转为32位2进制显示,第32位为符号位

按位与(&)

var a = 9;  // 1001
var b = 5;  // 0101

a & b;      // 0001 即 1

按位或(|)

var a = 9;  // 1001
var b = 5;  // 0101

a | b;      // 1101 即 13

按位非(~)

var a = 9;  // 1001

~a;         // 0110 即 6

按位异或( ^ )

var a = 9;  // 1001
var b = 5;  // 0101

a ^ b;      // 1100 即 12

左移(<<)

var a = 9;  // 1001
var b = 5;

a << b;     // 100100000 即 288

有符号的右移(>>)

var a = 9;  // 1001
var b = -3; // -0101

a >> 3;     // 1
b >> 3;     // -1

无符号的右移(>>>)

var oldValue = -64; //equal to binary 111111111111111111111111110 00000
var newValue = oldValue >>> 5; //equal to decimal 134217726

乘性操作符(Multiplicative Operators)

  • 一个Infinity数与0相乘,结果是NaN
  • Infinity被Infinity除,结果是NaN
  • 零被零除,结果是NaN
  • 非零有限数被零除,结果是Infinity或-Infinity
  • 被除数是无限大数,除数为有限大数,取模,结果是NaN

关系操作符(Relational Operators)

字符串比较的是想通位置每个字符的字符编码的值:

'Back' < 'at';  // true 大写B的编码小于a
'23' < '3';     // true <两边都是字符,而'2'小于'3'

任何操作数与NaN进行比较,结果都是false

'a' > 3;    // false
NaN > 3;    // false
NaN <= 3;   // false

相等操作符(Equality Operators)

非全等比较算法(The Abstract Equality Comparison Algorithm)

假使有x == y

  1. Type(x)与Type(y)一致
    • Type(x)为undefined,true
    • Type(x)为null,true
    • Type(x)为Number
      • x是NaN,false
      • y是NaN,false
      • x与y的数字类型的值相同,true
      • x为-0,y为+0,true
      • x为+0,y为-0,true
      • false
    • Type(x)为String, 如果x与y是等长等字符的字符序列,true,否则, false
    • Type(x)为Boolean,x与y同为真或假时,true,否则,false
    • x与y引用的同一个对象,true,否则,false
  2. x为undefined,y为null,true
  3. x为null,y为undefined,true
  4. x为Number,y为String或Boolean,返回 x == ToNumber(y)
  5. x为String或Boolean,y为Number,返回 ToNumber(x) == y
  6. x为String或Number,y为Object,返回 x == ToPrimitive(y)
  7. x为Object,y为String或Number,返回 ToPrimitive(x) == y
  8. 返回false
注意

等号是不可传递的:

new String('a') == 'a'    // true
'a' == new String('a');     // true

new String('a') == new String('a');     // false

label语句

ECMAScript没有goto语句,但是有label语句来满足循环嵌套中的跳转问题:

var num = 0;

outer:
for (var i = 0; i < 10; ++ i) {
    inner:
    for (var j = 0; j < 10; ++ j) {
        if ( i == 5 && j == 5 ) {
            break outer;
        } else if (j == 3) {
            continue inner;
        }
        
        ++ num;
    }
}

alert(num);     // 49

break outer会中断outer的执行,也就是整个循环嵌套,continue inner则会跳过inner当前的执行,到下一次循环

严格模式下arguments对象的限制

  1. 严格模式下,不允许访问arguments.callee和arguments.caller属性,主要体现在arguments.[[Get]]内部方法
  2. 严格模式下,arguments的索引器对应的那些属性,仅仅是传递的参数值的拷贝,并不存在与形参的相互关联性
  3. 严格模式下,arguments的callee和caller的特性被设置为[[Configurable:false]]。所以,使用Object.defineProperty等接口去设置或修改,都将抛出异常。
  4. 严格模式下,arguments,arguments.callee,arguments.caller,arguments.callee.caller也不允许再被赋值
'use strict';

void function fn(a) {
    alert(arguments[0]);
    a = 2;
    alert(arguments[0]);
}(1)

// 两次都是1

严格模式下,函数的参数不能有相同名称的变量:

function test(a, a) {'use strict';}

“JS高程”读书笔记0

结合ES5,重读《JavaScript高级程序设计》,希望把书读薄点儿。 刚开头,看了下数据类型,这里主要记录了Null、Boolean、Number

数据类型

6种数据类型:

  • 5种简单数据类型(基本数据类型):
    • Undefined Null Boolean String Number
  • 1种复杂数据类型:
    • Object

typof的返回值有: "undefined" "object" "boolean" "string" "number" "function"

ECMAScript关于typeof操作的定义The typeof Operator

null

对比发现,少了null,因为null表示一个空对象的指针:

typeof null; // "object"

undefined

// 未定义的
// var age

// 未初始化的
var msg;

// 调用方法
console.log(age);      // 报错
console.log(msg);      // undefined

但是typeof方法返回的是同一个值:

typeof age;      // undefined
typeof msg;       // undefined

undefined & null

undefined是派生自null的,ECMA-262规定他俩的相等性检测,返回true

undefined == null;       // true

这里处于比较的目的,是做了操作数转换的,因此,严格模式下并不相等:

undefined === null;      // false

number

采用IEEE754格式来表示整数和浮点数(因此也拥有IEEE754对于浮点数处理的bug)

Infinity无法参与数值计算,可用isFinite函数来检测:

var a = Number.MAX_VALUE + Number.MIN_VALUE;
console.log(isFinite(a));   // true

NaN——非数值(Not a Number),同时无法转换成数值,与任何值都不想等,可用isNaN检测:

NaN == NaN;          // false
isNaN(NaN);           // true
isNaN("10");        // false,可以被转换成数值
isNaN(false);     // false,可以被转换成数值
isNaN("number");    // true,非数值,同时,无法被转换成数值

isNaN同样可以用来检测对象,在基于对象调用isNaN函数时,会首先调用valueOf()方法,然后确定该方法的返回值是否可以转换为数值。如果不能,则基于这个返回值再调用toString()方法,再测试返回值。这个过程也是ECMAScript中内置函数和操作符的一般执行流程。

valueOf() & toString()

valueOf()与toString()是所有ECMAScript对象拥有的内置方法。操作对象时,valueOf()和toString()会被隐式的调用。

valueOf()方法的目的是将对象转换成最有意义的原始值([[PrimitiveValue]])。即ECMAScript的5种基本类型中的三种,布尔值、数字、字符串。

true.valueOf();          // true
1..valueOf();         // 1
'str'.valueOf();      // "str"
null.valueOf();           // TypeError
undefined.valueOf();  // TypeError
({k: 'v'}).valueOf();   // {k: 'v'},对象本身
[1,2,3].valueOf();       // [1,2,3],数组本身

ECMAScript-262中这样描述valueOf()方法:

When the valueOf method is called, the following steps are taken:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. If O is the result of calling the Object constructor with a host object (15.2.2.1), then
    • Return either O or another value such as the host object originally passed to the constructor. The specific result that is returned is implementation-defined.
  3. Return O.

当valueOf方法被调用时,会调用内置的ToObject,并将this作为参数传进去。ToObject检测会根据参数类型进行数值的转换:

  • Undefined - 抛出TypeError异常
  • Null - 抛出TypeError异常
  • Boolean - 创建一个Boolean对象,调用ToBoolean生成[[PrimitiveValue]]
  • Number - 创建一个Number对象,调用ToNumber生成[[PrimitiveValue]]
  • String - 创建一个String对象,调用ToString生成[[PrimitiveValue]]
  • Object - 对象本身

toString()方法的目的是将对象转换成一个有意义的字符串。

true.toString();                             // "true"
1..toString();                                    // "1"
'str'.toString();                             // "str"
null.toString();                              // TypeError
Object.prototype.toString.call(null);            // "[object Null]"
undefined.toString();                         // TypeError
Object.prototype.toString.call(undefined);       // "[object Undefined]"
({k: 'v'}).toString();                          // "[object Object]"
[1,2,3].toString();                              // "1,2,3"

ECMAScript对象的大多数操作的转换结果是字符串,这两个方法的结果是相同的。但是如果操作的对象为Number、Boolean或者Date,结果就不同了。

简单来说,在对像操作的隐式转换时,先根据前文线索(hint),如果hint是String,即字符串操作,则先调用toString()方法,没有返回值,再调用valueOf()方法;如果hint是Number,则正好相反。如果没有hint,则默认hint为Number。参见[[DefaultValue]] (hint)

var obj = {
    toString: function() {
        return 'invoke toString';
    },
    valueOf: function() {
        return 123;
    }
};

obj + '!';        // "123!"
alert(obj);       // "invoke toString"

关于toString和valueOf的转换见此文Conversion, toString and valueOf。有时间,再把他翻译了

关于对象的原始值(Object to PrimitiveValue)的转换,可以参考这篇文章Object-to-Primitive Conversions in JavaScript

关于+操作,首先计算左边表达式,调用GetValue()取得左边表达式的值;再计算右边表达式,调用GetValue()取得右边的值;之后将左边value求原始值(primitiveValue),再求右边原始值;如果左边或者右边原始值是string,则调用ToString拼接字符串;否则,调用ToNumber求和。参见The Addition operator ( + )

数值转换

有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。

Number

Number()可以将任何非数值,转换为数值。Number ( [ value ] ),将调用ToNumber来计算返回值:

  • Undefined - NaN
  • Null - +0
  • Boolean - true -> 1,false -> 0
  • Number - 原值,不做转换
  • Object -

对于字符串的操作比较复杂:

  • 只包含数字 - 转换为10进制数值,前导的0会被忽略,
  • 只包含有效浮点数 - 转为浮点数值,忽略前导0
  • 只包含有效十六进制数 - 转为相同的十进制数值
  • 空字符串 - 0
  • 不符合上述条件的 - NaN
Number("00123");       // 123
Number("01.1");         // 1.1
Number("0xf11");        // 3857
Number("");             // 0
Number("00xf11");       // NaN
parseInt()

parseInt(string, radix)方法,用于将字符串(string)转为响应进制(radix)的整形数字。

字符串(string)将忽略前导0、空格和其他制表符(\n、\t、\r)。

radix未定义或者是0,则默认以10进制计算。除非string是以0x或者0X开头的(将以16进制计算)。如果设置了radix为16进制,可以选择是否以0x0X开头。

ES3(ECMAScript-262 第三版)中会默认以0开头的,浏览器实现上可以按8进制计算,或者按10进制,但是ES5中已经去掉,即按10进制计算。类似的,《高程(第2版)》第3.4节Number类型(27页),关于parseInt('070')为默认以8进制计算的说法,目前是不适用的

parseInt('00000012.2');              // 0
parseInt('  0000 \t\n\r 12.2');       // 0
parseInt('  \t\r\n000012.2');     // 12
parseInt(' 0000012 ') * 2;          // 24
0000012 * 2;                      // 10

parseInt被调用时:

  • 去掉前导的空格和制表符
  • 设置为正数
  • 如果-开头,则为负数
  • 设置位数radix
  • 去掉0x或者0X前缀,如果剩余为空,返回NaN
  • 遇到第一个数字,开始解析,直到遇到第一个非数字,停止解析,返回结果

参见parseInt (string , radix)

parseFloat()不再介绍,详情见parseFloat (string)

测验

这里有一套题Are You a JavaScript Guru? Try This Test(需翻墙),其中至少有十道题都多多少少涉及这一部分相关的内容:

  1. ++Math.PI
  2. (0.1 + 0.2) + 0.3 == 0.1 + (0.2 + 0.3)
  3. typeof NaN
  4. typeof typeof undefined
  5. a = {null:null}; typeof a.null;
  6. a = "5"; b = "2"; c = a * b;
  7. a = "5"; b = 2; c = a+++b;
  8. isNaN(1/null)
  9. (16).toString(16)
  10. 016 * 2
  11. ~null
  12. "ab c".match(/\b\w\b/)

答案是(请自行翻译):

1100011011101000001101001011101100011101001100011101011110011100101101101101011100111101011110001110011101111110011100111000001000001000001000001100101011101000001100110110000111011001110011110010110000010000010000010000011001110111010000010001011011101110101110110111000101100101111001010001010000010000010000010000011010010111010000010001011100111110100111001011010011101110110011110001010000010000010000010000011010110111010000010001011011111100010110101011001011100011111010010001010000010000010000010000011011010111010000011000111000010000010000010000010000011011110111010000011011110000010000010000010000011100010111010000011001101100001110110011100111100101100000100000100000100000111001101110100000100010110001110000100010100000100000100000100000110001110000101110100000110010111000100000100000100000100000110001110001101110100000101101110001100000100000100000100000110001110010101110100000101101110000010001011000111000101000001011101 

这里还有一套题Test – Are you a Javascript Guru?(表打我,鲁迅先生《野草集》都有“我家门前有两棵树,一颗是枣树,另一颗还是枣树”的经典,我也用下……),其中至少五道题与这次内容相关。

计算result的指:

  1. var result = [10] + 1;
  2. var result = ['a', 'b', 'c'] + "";
  3. var result = 'a' + 5;
  4. var result = 3.75 | 0;
  5. var result = 65 / 'a';
  6. var ob = {"10": 1}; ob[10] = 2; ob[[1, 0]] = 3; var result = ob["10"] + ob[10] + ob[[1, 05. ]];
  7. var $ = {"": String}; var result = !!$([]);
  8. var result = (' \t\r\n ' == 0);
  9. var a = new String("123"); var b = "123"; var result = (a === b);
  10. var a = {key: 1}; var b = {key: 1}; var result = (a == b);

答案是(请自行翻译):

11000110111010000010001011000111000011000110001010000010000010000010000011001010111010000010001011000011011001100010101100110001110001010000010000010000010000011001110111010000010001011000011101011000101000001000001000001000001101001011101000001100111000001000001000001000001101011011101000001001110110000110011101000001000001000001000001101101011101000001101111000001000001000001000001101111011101000001100110110000111011001110011110010110000010000010000010000011100010111010000011101001110010111010111001011000001000001000001000001110011011101000001100110110000111011001110011110010110000010000010000010000011000111000010111010000011001101100001110110011100111100101

这里还有一套专门针对数值转换的题目——JavaScript Quiz [x + 0 == x - 0]

PWPO-前瞻性Web性能优化

原文地址是:http://calendar.perfplanet.com/2012/proactive-web-performance-optimization

文章作者Marcel Duran,也是YSlow的作者,twitter前端工程师,之前是Yahoo性能团队leader

简单意译了一下,看官勿喷。同时,期望能够在未来i版上使用此方式

PWPO——Proactive Web Performance Optimization,姑且翻译作前瞻性Web性能优化。

开发者对于Web性能必须时刻保持警惕,尤其是在发布新版本迭代新功能时,bug修复或者其他乱七八糟,看似八竿子打不着的事情,都可能会影响到性能,破坏终端用户的体验。

正因此,必须牢记并在每一次的开发和提交中践行Web性能优化的最佳实践。而恰当的在开发中使用工具,可以帮助我们找到潜在的性能问题,

最坏的情景:没有性能检测手段

在开发周期中,没有经过性能检测的代码,天知道面对终端用户时是什么情况。更糟的是,不论好与坏,我们都是没法度量,也没有线索去改进的。如果我们引入如下性能衰退图的话,终端用户就是那个无法忍受糟糕用户体验而发起红色警报的。幸运的话我们可以根据一些忠实的用户反馈,迫使开发者去解决这个问题。当然解决问题的时间可能会持续很久,直到没有人关心,然后用户忍无可忍的放弃掉应用。类似下图:

最坏的情景:没有性能检测手段

  • 开发和构建应用
  • 测试确保无碍
  • 发布应用
  • 用户或喜或气(弃)
  • 愤怒的用户做出反馈
  • 根据反馈,改进

好一些的情况:RUM

利用真实用户监测(Real User Measurement, aka RUM),为应用程序提供数据。收集包括如带宽,页面加载时间等数据,进行监测和估计最终用户的体验是什么样的。在性能衰退图下,跟据RUM,可以获得性能问题。即使这样,仍然给了最终用户一个痛苦不愉快的经历。被动的等待问题发现,然后修复,并在下个迭代中发布——这是很多网站目前使用的方式。

好一些的情况:RUM

  • 开发和构建应用
  • 测试确保无碍
  • 发布应用
  • 用户或喜或气(弃)
  • RUM结果不理想
  • 改进

YSlow

YSlow一开始是只能通过手动执行,基于一组性能检测规则对页面的进行静态分析,报告检测出的问题。后来开始在真实浏览器中安装YSlow做自动化尝试。

2011后,新的YSlow可以在命令行下利用NodeJS执行。对HAR(HTTP Archieve)文件进行静态分析的。2012年年初,YSlow也可以结合PhantomJS (命令行下没有界面的WebKit浏览器)工作,为一个的URL通过命令行进行分析,并给出结果。 YSlow还专门为PhantomJS还提供了两种新的输出格式:TAPJUnit 。这两种技术都可以配置设定通过与否的阈值。

更好的方式:RUM + YSlow on CI

基于YSlow为PhantomJS的定制,可以很容易将其整合到持续集成系统中(continous integration, aka CI) 。如果有一个出现性能衰退,就会打破了构建过程,从而避免了潜在的性能问题发布在生产环境中。有效避免最终用户产生很坏的体验。

YSlow的wiki中有解释如何将YSlow和Phantom集成到Jenkins的文章。值得一提的是--threshold参数,会作为最终的性能验收标准的最终配置。

更好的方式:RUM + YSlow on CI

  • 开发和构建应用
  • 测试确保无碍
  • 分析YSlow通过与否
  • 失败,则重新开发修复
  • 通过则发布应用
  • 希望用户都ok
  • 持续进行RUM
  • 性能改进

最好的方式:RUM + YSlow on CI + WPT

如果是在开发过程中就一直践行着Web性能最佳实践的应用,YSlow的得分就会稳定在A或B中而失去意义。此时的YSlow已经不再能够预警微小的新能衰退了。

此时的YSlow得分,看似令人满意,但并不意味着它的得分就是真实浏览器环境下最终用户体验的得分。所以,接下来的优化将着眼于真实浏览器中的用户体验。通过多次取样真实浏览器下的体验值,然后与上线的性能阈值去比对,来最终确认是否上线。

WebPagetest来自动化真实浏览器下的性能测试,是个不错的选择,因为他有可供NodeJS调用的API(可参考该文章xmas-gift-webpagetest-api-swiss-army-knife)。

最好的方式:RUM + YSlow on CI + WPT

  • 开发和构建应用
  • 测试确保无碍
  • 分析YSlow通过与否
  • 失败,则重新开发修复
  • YSlow通过,则利用WPT做性能测试,并与当前生产环境的性能比对
  • 失败,则重新来过
  • 如果上两层测试都通过,则部署
  • 期待获得更多用户的满意
  • 持续进行RUM
  • 总是有可供性能提升的地方

在对新版本(分支)或分支进行WPT测试时,理想情况是有一个单独的与生产环境一样的沙箱,从而让WPT的结果尽可能的与生成环境下最终用户的体检结果一致。

结语

尽可能主动出击,防微杜渐,将Web性能的最佳实践及检测通过YSlow等方式加入到CI中,将性能衰退扼杀在摇篮里,给最终用户创造更好的体验。

附注

Proactive Web Performance Optimization

Fork me on GitHub