Coder Social home page Coder Social logo

effective-javascript's Introduction

Effective JavaScript

68 Specific ways to harness the power of javascript.

Tools

  • Wunderlist - 定制学习目标
  • FireFox -> 代码草稿纸 - 运行书中的JavaScript例子。
  • GitHub -> Issues - 分享学习笔记

License

MIT

effective-javascript's People

Contributors

xiaoluoboding avatar

Stargazers

 avatar

Watchers

 avatar  avatar

effective-javascript's Issues

第4条:原始类型优于封装对象

笔记

  • JavaScript有5个原始值类型:布尔值、数字、字符串、null和undefined。
  • String对象时一个真正的对象,每一个String对象都是一个单独的对象。不能使用内置的操作符比较两个截然不同的String对象。
  • 当对原始值提取属性和进行方法调用时,它表现得就像已经使用了对应的对象类型封装了该值一样(隐式转换)。
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

var s = new String("hello");
s + " world";
/*
hello world
*/

s[4];
/*
o
*/

typeof "hello";
/*
string
*/

typeof s
/*
object
*/

var s1 = new String("hello");
var s2 = new String("hello");

s1 === s2;
/*
false
*/

提示

  • 当做相等比较时,原始类型的封装对象与原始值行为不一样。
  • 获取和设置原始类型值的属性会隐式地创建封装对象。

第1条:了解你使用的JavaScript版本

提示

  • 决定你的应用程序支持JavaScript的哪些版本
  • 确保你使用的任何JavaScript的特性对于应用程序将要运行的所有环境都是支持的
  • 总是在执行严格模式检查的环境中测试严格代码
  • 当心连接那些在不同严格模式下有不同预期的脚本

第10条:避免使用with

笔记

  • 人们总是很难预测一个特定的对象是否被改变,人力不可预测的特性对于优化编译器同样不可预测。
  • 使用 with 会让函数的行为变得不可预测
  • JavaScript 作用域可被表示为高效的内部数据结构,变量查找会非常快速。
  • with 代码块需要搜索对象的原型链来查找 with 代码块里的所有变量,因此运行速度远远低于一般的代码块。
function f(x, y) {
  with (Math) {
    return min(round(x), sqrt(y))
  }
}

f(2, 9)

/*
2
*/

不可预测会不会有人给 Math 添加 xy 属性

function f(x, y) {
  with (Math) {
    return min(round(x), sqrt(y))
  }
}

Math.x = 0
Math.y = 0

f(2, 9)

/*
0
*/

更好的办法是将局部变量显式地绑定到相关属性上

function f(x, y) {
  var min = Math.min, round = Math.round, sqrt = Math.sqrt;
  return min(round(x), sqrt(y));
}

Math.x = 0
Math.y = 0

f(2, 9)


/*
2
*/

提示

  • 避免使用 with 语句。
  • 使用简短的变量名代替重复访问的对象。
  • 显式地绑定局部变量到对象属性上,而不要使用 with 语句隐式地绑定它们。

第9条:始终声明局部变量

笔记

  • 如果忘记将变量声明为局部变量,那么该变量将会被隐式地转变为全局变量
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

function swap(a, i, j) {
  temp = a[i];
  a[i] = a[j];
  a[j] = temp;
}

var arr = [ 100, 1000, 10000 ];
for (var i = 0; i < arr.length; i++) {
  for (var j = i+1; j < arr.length; j++) {
    swap(arr, i, j);
  }
}

arr

/*
10000,1000,100
*/

提示

  • 始终使用var声明新的局部变量
  • 考虑使用lint工具帮助检查未绑定的变量

第7条:视字符串为16位的代码单元序列


提示

  • JavaScript字符串由16位的代码单元组成,而不是由Unicode代码点组成。
  • JavaScript使用两个代码单元表示216及其以上的Unicode代码点。这个两个代码单元被称为代理对。
  • 代理对甩开了字符串元素计数,lengthcharAtcharCodeAt方法以及正则表达式模式(例如".")收到了影响。
  • 使用第三方的库编写可识别代码点的字符串操作。
  • 每当你使用一个含有字符串操作的库时,你都需要查阅库该库文档,看它如何处理代码点的整个范围。

第3条:当心隐式的强制转换

笔记:

  • 字符串 + 数字 = 字符串
  • NaN是JavaScript中唯一一个不等于其自身的值,可以用过检查一个值是否等于其自身来判断该值是否是NaN。
  • 抽象为一个工具函数:
    function isRealyNaN(x) {
        return x !== x
    }
  • 字符串的强制转换远比数字的强制转换更常见、更有用。
  • JavaScript中有7个假值:false0-0""NaNnullundefined,其他所有的值都为真值。
  • 检查参数是否为undefined更为严格的方式是使用typeof
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

3 + true
/*
4
*/

// 抽象为一个工具函数
function isReallyNaN(x) {
  return x !== x;
}
isReallyNaN(NaN);
/*
true
*/
isReallyNaN(2);
/*
false
*/

"J" + { toString: function() { return "S"; } };
/*
JS
*/

2 * { valueOf: function() { return 3; } };
/*
6
*/

提示:

  • 类型错误可能被隐式的强制转换所隐藏。
  • 重载的运算符+是进行加法运算还是字符串连接操作取决于其参数类型。
  • 对象通过valueOf方法强制转换为数字,通过toString方法强制转换为字符串。
  • 具有valueOf方法的对象应该实现toString方法,返回一个valueOf方法产生的数字的字符串表示。
  • 测试一个值是否为未定义的值,应该使用typeof或者与undefined进行比较而不是使用真值运算。

第2条:理解JavaScript的浮点数

笔记:

  • JavaScript中所有的数字都是双精度浮点数。
  • 按位或运算符 -> 合并两个比特序列
  • 当我们关心精度时,要小心浮点数的局限性。有效的解决方式是尽可能地采用整数值运算,因为整数在表示时不需要舍入。
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

8 | 1
/*
9
*/

(8).toString(2)
/*
1000
*/

parseInt('1001',2)
/*
9
*/

0.1 + 0.2
/*
0.30000000000000004
*/

(0.1 + 0.2) + 0.3
/*
0.6000000000000001
*/

0.1 + (0.2 + 0.3)
/*
0.6
*/

(10 + 20) + 30
/*
60
*/

10 + (20 + 30)
/*
60
*/

提示:

  • JavaScript的数字都是双精度的浮点数
  • JavaScript中的整数仅仅是双精度浮点数的一个子集,而不是一个单独的数据类型
  • 位运算符将数字视为32位的有符号整数

第8条:尽量少用全局对象

笔记

  • 定义全局变量会污染共享的公共命名空间,并可能导致意外的命名冲突
  • 全局命名空间是JavaScript程序中独立的组件进行交互的唯一途径
  • JavaScript的全局命名空间也被暴露为在程序全局作用域中可以访问的全局对象,该对象作为this关键字的初始值
  • 添加或修改全局变量会自动更新全局对象,类似地,更新全局对象也会自动地更新全局命名空间
  • 特性检测是一种使得程序在平台特性集合的变化中依旧健壮的相对简单的方法
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

function averageScore(players) {
    var i, n, sum;
    sum = 0;
    for (var i = 0, n = players.length; i < n; i++) {
        sum += players[i];
    }
    return sum / n;
}

averageScore([7, 8, 9])

/*
8
*/
// 添加或修改全局变量会自动更新全局对象
this.foo;
/*
undefined
*/
foo = "global foo";
this.foo;
/*
global foo
*/

// 更新全局对象也会自动地更新全局命名空间
var foo = "global foo";
this.foo = "changed";
foo
/*
changed
*/ 

全局对象的**特殊用途:**由于全局对象提供了全局环境的动态反应机制,所以可以使用它查询一个运行环境,检测在这个平台下哪些特性是可用的。

typeof(this.JSON)
/*
object
*/

提示

  • 避免声明全局变量
  • 尽量声明局部变量
  • 避免对全局对象添加属性
  • 使用全局对象来做平台特性检测

第6条:了解分号插入的局限

笔记

  • 分号插入的第一条规则:
    • 分号只能在一行、一个代码块和一段程序借宿的地方省略分号。
  • 分号插入的第二条规则:
    • 分号插入式一种错位校正机制。
  • 有5个明确有问题的字符需要密切注意:([+-/。每一个都能作为一个表达式运算符或者一条语句的前缀,依赖于具体上下文。
  • JavaScript允许逗号分隔表达式。逗号分隔表达式从左至右以此执行,并返回最后一个表达式的值。

function square(x) {
    var n = +x
    return n * n
}
square(4)
/*
16
*/

function area(r) { r = +r; return Math.PI * r * r }
area(4)
/*
50.26548245743669
*/

function add1(x) { return x + 1 }
add1(4)
/*
5
*/

function area(r) { r = +r return Math.PI * r * r }
area(4)
/*
Exception: SyntaxError: missing ; before statement
@Scratchpad/2:1
*/

提示

  • 仅在}标记之前、一行的结束和程序的结束处推导分号。
  • 仅在紧接着的标记不能被解析的时候推导分号。
  • 在以([+-/字符开头的语句前绝不能省略分号。
  • 当脚本连接的时候,在脚本之间显示地插入分号。
  • returnthrowbreakcontinue++--的参数之前绝不能换行。
  • 分号不能作为for循环的头部或空语句的分隔符而被推导出。

第5条:避免对混合类型使用==运算符

笔记

  • 当两个参数属于同一类型时,=====运算符的行为是没有区别的。这时,最好使用严格相等运算符。
  • 显式地定义转换的逻辑能确保你不混混淆==运算符的强制转换规则,而且免除了读者不得不查找或记住这些规则的麻烦。
参数类型1 参数类型2 强制转换
null undefined 不转换,总是返回true
nullundefined 其他任何非nullundenfined的了类型 不转换,总是返回false
原始类型:string、number或boolean Date对象 将原始类型转换为数字;将Date对象转换为原始类型(优先尝试toString方法,再尝试valueOf方法)
原始类型:string、number或boolean 原始类型:string、number或boolean 将原始类型转换为数字
/*
 * 这是一张 JavaScript 代码草稿纸。
 */

var date = new Date("1999/12/31");
date == "1999/12/31";
/*
false
*/

date.toString();
/*
Fri Dec 31 1999 00:00:00 GMT+0800
*/

function toYMD(date) {
  var y = date.getYear() + 1900,
      m = date.getMonth() + 1,
      d = date.getDate();
  return y
      + "/" + (m < 10 ? "0" + m : m)
      + "/" + (d < 10 ? "0" + d : d);
}

toYMD(date) === "1999/12/31";
/*
true
*/

提示

  • 当参数类型不同时,==运算符应用了一套难以理解的隐式强制转换规则。
  • 使用===运算符,使读者不需要涉及任何的隐式强制转换就能明白你的比较运算。
  • 当比较不同类型的值时,使用你自己的显示强制转换使程序的行为更清晰。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.