你不知道的JavaScript(下卷)读书笔记

856 阅读3分钟

第一部分 起步上路

第1章 深入编程


1.8 块

:代码中的一系列语句组织到一起,称之为块。

1.11 函数

函数:可复用的代码片段。

作用域:严格意义上指词法作用域,指变量的一个集合以及如何通过名称访问这些变量的规则。

1.13 小结

核心的编辑积木块:

• 在值上执行动作需要运算符

• 执行各种类型的动作需要值和类型,比如,对数字进行数学运算,用字符串输出 。 • 在程序的执行过程中需要变量来保存数据(也就是状态)。

• 需要 if 这样的条件判断来作出决策。

• 需要循环来重复任务,直到不满足某个条件。

• 需要函数将代码组织为逻辑上可复用的块。

第2章 深入Javascript


2.1 值与类型

对象:一个组合值,可以为其设置属性(命名的位置),每个属性可以持有属于自己的任意类型的值。

数组: 持有(任意类型)值的对象

和函数属于****

真与假

JavaScript 中“假”值的详细列表如下:

• ""(空字符串)

• 0、-0、NaN( 无效数字)

• null、undefined

• false

任何不在“假”值列表中的值都是“真”值。

2.2 变量

提升:使用关键字 var 声明一个变量,无论变量 出现在一个作用域中的哪个位置,这个声明都属于整个作用域,在其中到处都是 可以访问的。

第二部分 ES6及更新版本

第2章 语法


2.1 块作用域声明

如何创建一个块作用域

1、ES5的方式有两种:普通的函数声明立即调用函数表达式

2、ES6的方式有三种:let声明、const声明、块作用域函数

(1)let 声明

var a = 2;
{
    let a = 3;
    console.log( a );  // 3 
}
console.log( a );   // 2

{
    console.log( a ); // undefined
    console.log( b ); // ReferenceError!
    var a;
    let b;
}

过早访问 let 声明的引用导致的这个 ReferenceError 严格说叫作临时死亡区(Temporal Dead Zone,TDZ)错误——你在访问一个已经声明但没有初始化的变量。

(2)const 声明 const 用于创建块作用域常量

{
    const a = 2;
    console.log( a );      // 2
    a = 3;      // TypeError!
}

(3)块作用域函数


{
    foo(); // 可以这么做!
    function foo() {
         // ..
    } 
}
foo();  // ReferenceError!

foo()函数声明在{ .. }块内部,ES6支持块作用域。所以在块外不可用。

2.3 默认参数值

思考如何设定函数参数默认值

function foo(x,y) {
    x = x || 11;
    y = y || 31;
    console.log( x + y );
}
foo();
foo( 5, 6 );
foo( 5 );
foo( null, 6 );
// 42
// 11
// 36
// 17

陷阱

如果对于一个参数你需要能够传入被认为是 falsy(假)的值。

foo( 0, 42 ); // 53 <--哎呀,并非42

默认值表达式

  1. 默认值表达式是惰性求值的,参数的值省略或者为 undefined 的时候运行。
function bar(val) {
     console.log( "bar called!" );
     return y + val;
}
 function foo(x = y + 3, z = bar( x )) {
     console.log( x, z );
}
var y = 5;
foo();
foo( 10 );
y = 6;
foo( undefined, 10 );
// "bar called"
// 8 13
// "bar called"
// 10 15
// 9 10
  1. 函数声明中形式参数是在它们自己的作用域中,而不是在函数体作用域中。这意味着在默认值表达式中的标识符引用首先匹配到形式参数作用域,然后才会搜索外层作用域。
 function foo( x = w + 1, y = x + 1, z = z + 1 ) {
     console.log( x, y, z );
 }
 foo();                   // ReferenceError

z + 1中的z发现z是一个此刻还没初始化的参数变量,所以它永远不会试图从外 层作用域寻找 z。

2.4解构

  • 解构 === 结构化赋值,
  • 解析包括数组解构和对象解构

已声明变量赋值

  1. 解构是一个通用的赋值操作,不只是声明。
function bar() {
    return {
        x: 4, y: 5, z: 6
    }; 
}

function foo() {
    return [1,2,3];
}

 var a, b, c, x, y, z;
 [a,b,c] = foo();
 ( { x, y, z } = bar() );
 console.log( a, b, c );    // 1 2 3
 console.log( x, y, z );    // 4 5 6
  1. 赋值表达式 (a、y 等 ) 并不必须是变量标识符,任何合法的赋值表达式都可以。
var o = {};
[o.a, o.b, o.c] = foo();
( { x: o.x, y: o.y, z: o.z } = bar() );
console.log( o.a, o.b, o.c );       // 1 2 3
console.log( o.x, o.y, o.z );       // 4 5 6
  1. 在解构中使用计算出的属性表达式。
var which = "x",
o = {};
( { [which]: o[which] } = bar() );
console.log( o.x );                 // 4

[which]: 这一部分是计算出的属性,结果是 x——要从涉及的对象解构出来作为赋值源的属性。o[which] 部分就是一个普通的对象键值引用,等价于o.x作为赋值的目标。

  1. 用一般的赋值来创建对象映射 / 变换,比如:
var o1 = { a: 1, b: 2, c: 3 },
o2 = {};
( { a: o2.x, b: o2.y, c: o2.z } = o1 );
console.log( o2.x, o2.y, o2.z );    // 1 2 3
  1. 把一个对象映射为一个数组,比如:
var o1 = { a: 1, b: 2, c: 3 },
a2 = [];
( { a: a2[0], b: a2[1], c: a2[2] } = o1 );
console.log( a2 );  // [1,2,3]

或者

var a1 = [ 1, 2, 3 ],
o2 = {};
[ o2.a, o2.b, o2.c ] = a1;
console.log( o2.a, o2.b, o2.c );    // 1 2 3
  1. 把数组重排序到另一个:
var a1 = [ 1, 2, 3 ],
a2 = [];
[ a2[2], a2[0], a2[1] ] = a1;
console.log( a2 );                  // [2,3,1]
  1. 用临时变量解决“交换两个变量”
var x = 10, y = 20;
[ y, x ] = [ x, y ];
console.log( x, y );                // 20 10

重复赋值

var { a: { x: X, x: Y }, a } = { a: { x: 1 } };
X;  // 1
Y;  // 1
a;  // { x: 1 }
     
( { a: X, a: Y, a: [ Z ] } = { a: [ 1 ] } );
X.push( 2 );
Y[0] = 10;

X;  // [10,2]
Y;  // [10,2]
Z;  // 1

2.6 对象字面量扩展

设定 [[Prototype]]

要为已经存在的对象设定 [[Prototype]],可以使用 ES6 工具 Object.setPrototypeOf(..)。 考虑:

var o1 = { 
    // ..
};
var o2 = { 
    // ..
};

Object.setPrototypeOf( o2, o1 );

super对象

通常把 super看作只与类相关。但是,鉴于 JavaScript的原型类而非类对象的本质,super 对于普通(plain)对象的简洁方法也一样有效,特性也基本相同。

简洁方法

var o = {
    x: function(){
        // .. 
    },
    y: function(){
        // ..
    } 
}
 
// 在 ES6 中则可以:
var o = {
    x() {
        // .. 
    },
    y() {
        // ..
    } 
}

考虑:

var o1 = {
     foo() {
         console.log( "o1:foo" );
     }
};
 var o2 = {
     foo() {
         super.foo();
         console.log( "o2:foo" );
     }
 };
 Object.setPrototypeOf( o2, o1 );
 o2.foo();          // o1:foo
                    // o2:foo

2.7 模板字面量

标签模板字面量

function foo(strings, ...values) {
    console.log( strings );
    console.log( values );
}
 
var desc = "awesome";
foo`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]

解析:这是一类不需要( .. )的特殊函数调用。 标签(tag)部分,即..字符串字面量之前的 foo 这一部分 , 是一个要调用的函数值。实际上,它可以是任意结果为函数的 表达式,甚至可以是一个结果为另一个函数的函数调用,就像下面这样:

function bar() {
  return function foo(strings, ...values) {
      console.log( strings );
      console.log( values );
  }
}
var desc = "awesome";
bar()`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]

第一个参数strings是一个由所有普通字符串(插入表达式之间的部分)组成的 数组。得到的 strings 数组中有两个值:"Everything is" 和 "!"。 第二个参数values是已经求值的在字符串字面值中插入表达式的结果。所以显然我 们的例子中 values 的唯一元素是 "awesome"。

应用场景

全球化、本地化等的特殊处理

例:

  1. 计算出一个适当的字符串并将其返回,像使用非标签字符串字面量一样把标签字符串字面量作为一个值来使用:
function tag(strings, ...values) {
    return strings.reduce( function(s,v,idx){
        return s + (idx > 0 ? values[idx-1] : "") + v;
    }, "" );
}
var desc = "awesome";
var text = tag`Everything is ${desc}!`;
console.log( text );    // Everything is awesome!
  1. 把数字格式化为美元表示法(类似于简单的本地化):
function dollabillsyall(strings, ...values) {
    return strings.reduce( function(s,v,idx){
         if (idx > 0) {
             if (typeof values[idx-1] == "number") {
            // 看,这里也使用了插入字符串字面量!
                 s += `?{values[idx-1].toFixed( 2 )}`;
             }
             else {
                s += values[idx-1];
        } }
         return s + v;
    }, "" );
}
var amt1 = 11.99,
amt2 = amt1 * 1.08,
name = "Kyle";
var text = dollabillsyall
`Thanks for your purchase, ${name}! Your
product cost was ${amt1}, which with tax
comes out to ${amt2}.`
console.log( text );
// Thanks for your purchase, Kyle! Your
// product cost was $11.99, which with tax
// comes out to $12.95.

原始(raw)字符串

通过.raw属性访问这 些原始字符串值:

function showraw(strings, ...values) {
  console.log( strings );
  console.log( strings.raw );
}
showraw`Hello\nWorld`;
// [ "Hello
// World" ]
// [ "Hello\nWorld" ]

ES6 提供了一个内建函数可以用作字符串字面量标签:String.raw(..)。它就是传出 strings 的原始版本:

console.log( `Hello\nWorld` );
// Hello
// World
console.log( String.raw`Hello\nWorld` );
// Hello\nWorld
String.raw`Hello\nWorld`.length;
// 12

2.8 箭头函数

  • 函数表达式,并不存在箭头函数声明。

  • 匿名函数表达式——没有用于递归或者事件绑定 / 解绑定的命名引用。

  • 内部的this是词法作用域,由上下文确定。

  • 有词法arguments——没有自己的arguments 数组,而是继承自父层——词法 super 和 new.target也是一样。