const a = `line1
	line2\n
	line3
`;

问题:除了换行还存在缩进空格,

const multiline = "line1\n"+
	"line2\n"+
	"line3";

一般经验是,null是给开发者用的,而undefined则是留给JavaScript用的,用来表示未赋值的内容。

在以往的经验中,只有在有意的模仿变量未被赋值的时候,才会使用undefined。当需要表示一个变量的值未知或者不适用的时候,常见的做法是使用null。

const s = "hello";
s.toUpperCase();//"HELLO"

在这个例子里s看起来像是一个对象(就好像它有一个函数属性,这里在访问它的函数属性)。但事实很清楚:s是基本的字符串类型。JavaScript所做的事情就是创建一个临时的string对象(该对象有一个yoUpperCase函数)。一旦这个函数被调用看,该临时对象就会被删除。

const s  ='hello';
s.rating = 3;
s.rating;//undefined

表达式是一个特殊的语句,它可以计算出一个值。可以认为非表达式语言是一条指令,而表达式语言是一个请求。

也即: 你不仅仅接收到一个命令,而且执行完命令后要返回结果。

表达式会产生一个值。运算符就是产生这个值所要做的事情。


一元正号(+)强制把字符串转换成数字,或者调整那些否定的值。


负数取余会带上被除数的符号,而不是除数的,这就防止这个运算符成为一个真正的模运算符。

10 % -3 //1
-10 % 3 //-1

短路求值:如果x是false,不管y的值是什么,结果都是false。同样,对于x||y,一旦x是true,就不用在计算y的值是什么,结果都是true.


逗号运算符:逗号运算符可以简单的将表达式组合起来:它会按顺序执行两个表达式,并返回第二个表达式的结果。如果想执行多个表达式,但只关心最后一个表达式的结果,使用逗号运算符就很方便。

let [x,y] = [1,2]
let z = (x++,y++);
console.log(z);//2

因为JavaScript 中所有数字都是双精度,JavaScript在执行位运算前先将数字转换成32位的整形,并在返回结果之前转换回来。


解构赋值语句一般有声明也有赋值/对象解构也可以在一个赋值语句中完成,但是这个语句必须被括号括起来。否则,JavaScript解释器会认为左边的部分是一个代码块。

const obj = {a:1,b:2,c:4,d:4};
// const {a,b,c} = obj; 
// const a,b,c ;
let a,b,c;
({a,b,c} = obj);
console.log(a,b,c);

有时候写function会这样创立:

const g = function f(s){
   	 //...
}

当一个函数被这样创立时,g这个名字具有较高的优先级,在引用函数时(在函数外),应该使用g;如果使用f引用函数则会得到一个undefined variable的错误。但我们可以在函数内部用f。也即在函数内部引用它本身的时候(称为递归调用)。

const g = function f(s) {
   if(s) console.log('f stopped');
   else {
       console.log('continue')
       f(true);
   }
}
f(1);//error
g(false);

bind方法可以给一个函数永久绑定this值。给函数绑定一个永久的this值可能会成为一个潜在的并且很难定位的bugs:因为使用bind之后,函数实际上已经不能再有效的使用call,apply,bind(再次)。


bind也可以实现柯里化效果呢。


形参只有在函数被调用的时候才存在(变成实参)。一个函数可能会被调用多次:每次函数调用开始时,参数才是真实存在的,在函数返回后参数就失去作用域了。


当某些变量不复存在时,JavaScript不一定会立即回收内存:它只是标记这些条目不再需要保留,垃圾回收进程会定期去回收。此处,闭包!


静态作用域指的是,在某个作用域内定义了某个函数(而不是调用函数),该作用域包含的所有变量也在该函数的作用域内。在JS中,静态作用域适用于全局作用域,块级作用域以及函数作用域。


全局作用域问题:

函数高度依赖它们被调用时的上下文(或者作用域)。整个程序中,任何地方的任何函数,都可以改变name的值(有意或无意)。而name和age这样的变量名太过普通,很可能被用到其他地方,用于其他用途。


闭包:故意将某个函数定义在一个指定的作用域中,并明确地指出它对该作用域所具备访问权限,通常称为这种形式为闭包(可以认为封闭了函数地作用域)。

let globalFunc;
{
	let blockVar = 'a'; //块级作用域变量
	globalFunc = function() {
		console.log(blockVar);
	}
}
globalFunc();

意义:无论在哪里调用globalFunc,它都有权限访问闭包内的变量,这里面隐含了一层重要的含义:当调用globalFunc时,尽管程序已经退出了变量blockVar的作用域,它仍然有权限访问它。在这个例子里,JavaScript注意到函数被定义在指定作用域内(并且这个函数可以在该作用域外被引用),所以该函数会持有该作用域的访问权限。


在闭包内定义的函数不但可以影响闭包的生命周期,它还允许访问一些正常情况下无法访问到的信息。


使用var声明的变量,JavaScript不会关心它是否有重复声明。


在全局作用域中使用严格模式后,它会应用于所有脚本代码,所以使用时要谨慎一点,很多流行的网站在部署前会整合所有的脚本,一旦某个脚本文件中开启了全局严格模式,那么所有的文件都会启用这个模块。所以通常不建议在全局作用域中使用严格模式。


当数组的元素未被赋值或已被删除时,map,filter,reduce就不会调用所传入的函数

const a = Array(10).map(x=>5)//[empty × 10]

const arr = [1,2,3,4,5]
delete arr[2];
arr.map(x=>1);//[1, 1, empty, 1, 1]

Array.prototype.join方法接收一个分隔符作为参数(在不指定时会用逗号作为默认值),返回连接了所有元素的字符串(包含未定义和删除的元素,这些元素会变成空数组,空元素,undefined或空字符串):

const a2 = [1,null,"hello","world",true,undefined];
delete a2[3];
a2.join('-');  //"1--hello--true-"

如果不用WeakMap,私有属性将永远不会跑出作用域,即使是它们引用的实例。

使用数学符号(#)来描述原型方法已经成为一种普遍的约定。例如,大家会经常看到Car.prototype.shift被简单写成Car#shift。


当使用关键字new来创建一个新的实例时,函数的原型属性就会变得很重要:新创建的对象可以访问其构造器的原型对象。对象实例会将它存储在自己的_proto_属性中。

关于原型,有一个重要的机制叫做动态调度(“调度”是方法调用的另一种说法)。当试图访问对象的某个属性或方法时,如果它不存在于当前对象中,JavaScript会检查它是否存在于对象原型中。


静态方法(也叫类方法):它不与实例绑定。在静态方法中,this绑定的是类本身,但通常使用类名来代替this是公认的最佳实践。


weakMap跟Map本质上是相同的,除了以下几点:

  • key 必须是对象。
  • WeakMap中的key可以被垃圾回收。
  • WeakMap不能迭代或者清空。

通常,只要还有地方在引用某个对象,JavaScript就会将它保留在内存中。例如》:如果有一个对象是Map中的key,那么只要这个Map存在,这个对象就会一直在内存中。但WeakMap却不是这样。正因为如此,WeakMap不能被迭代(因为在迭代中,暴露处于垃圾回收过程中的对象,是非常危险的)


因为WeakMap具备这些特性,才用它存储对象实例中的私有key。

const SecretHolder = (function(){
    const secrets = new WeakMap();
    return class{
   	 setSecret(secret){
   		 secrets.set(this,secret);
   	 }
   	 getSecret(){
   		 return secrets.get(this);
   	 }
    }
})();

const a = new SecretHolder();
const b = new SecretHolder();

a.setSecret('secret A');
b.setSecret('secret B');

a.getSecret();  //'secret A'
b.getSecret();  //'secret B'

这里也可以用普通的Map,但是这样会导致SecretHolder实例中的secret永远不会被垃圾回收!


Weak sets只能包含对象,这些对象可能会被垃圾回收,跟WeakMap类似,WeakSet中的值不能被迭代。事实上,weak sets 的唯一用处是判断对象是不是一个set。


错误可以在调用栈中的任一级别被捕获,如果它们没有被捕获,JavaScript解释器就会强行终止程序.这种错误被称为未被处理的异常或者未被捕获的异常,它会使程序崩溃。


如果一个类提供了一个符号方法Symbol.iterator,这个方法返回一个具有迭代行为的对象(比如:对象有next方法,同时next方法返回一个包含value和done的对象),那么这个类就是可迭代的


生成器是使用迭代器来控制其运行的函数。一般来说,函数会获取参数然后返回结果,但是函数调用者并没有办法控制该函数。当调用一个函数时,实际上是放弃了对函数的控制,直到函数返回。有了控制器,就可以在函数执行时对它进行控制。


生成器提供了两种能力:首先,是控制函数执行的能力,使函数能够分布执行;其次,是与执行中的函数对话的能力。


生成器与一般函数有两个不同的地方:

  • 函数可以通过使用域(yield),在其运行的任意时刻将控制权交还给调用方。
  • 调用生成器的时候,它并不是立即执行。而是会回到迭代器中。函数会在调用迭代器的next方法时执行。

生成器可以让它和其调用者进行双向交流。表达式可以计算出值,而yield也是一个表达式,所以它一定可以计算出一个值。

function* interrogate() {
	const name = yield "heihei";
	const color = yield "blackblack";
	return `${name}'s favorite color is ${color}.`;
}

const it = interrogate();
it.next();//{value: "heihei", done: false}
it.next('hei');//{value: "blackblack", done: false}
it.next('black');//{value: "hei's favorite color is black.", done: true}

在生成器的任何位置调用return 都会使得done的值变成true,而value的值则是任何被返回的值


使用生成器的东西并不总会在意done为true时value的值。如果在for...of循环中使用它,那么'c'是不会被打印的。
function* abc() {
	yield 'a';
	yield 'b';
	return 'c';
}

const it = abc;
for(let l of abc()){
	console.log(l);
}
//a
//b

纯函数:针对相同的输入,函数的返回值始终一样,并且没有副作用,这样它就变成了一个纯函数。

通过封装函数来避免重复是一个如此基础的概念,以至于它都有自己的缩略词DRY(don't repeat yourself)

<br/>

当函数被调用时,它是动态的,不过在调用之前,它与其他变量一样,是静态的。


管道:好比要频繁执行的一系列独立操作。任何时候当需要按照指定顺序执行一系列函数时,管道都是一个有用的抽象原则。


必须时刻注意回调函数的作用域:回调函数可以访问闭包内的所有内容。正是由于这个原因,回调函数的实际执行结果可能会跟预期的不一样。


Node确定其主导地位后,一个叫错误优先回调的约定产生了。由于回调使异常处理变得很棘手,所以需要一个标准化的方式将错误传到回调中。于是就出现了在回调中使用第一个参数来接收错误对象的约定。如果该对象为null或者undefined,就表示没有错误。


没有办法阻止回调函数被意外的调用两次,或者压根没有被调用。如果寄希望于它被调用且只被调用一次,那么结果只会令人失望,因为JavaScript中没有提供防止这种意外出现的保护机制。


事件:事件发射器可以广播事件,任何愿意监听(或者“订阅”)这些事件的人都可以去做这件事。