My Little World

《js 语言精髓与编程实践》

整本书看下来,稍微有些晦涩,很多理论概念被换了一种说法阐释,开阔思路,故整理笔记。

笔记按照篇章整理,没有固定逻辑。

第二章

1.语法关键字对语义逻辑的绑定结果,是对作用域的限定;
变量对位置性质的绑定结果,则是对变量生命周期的限定。

2.函数的6种声明标识符的方法(var,const,let,fucntion,class,import),他们声明的标识符在语法分析阶段就可以被识别。

3.关于值传递与引用传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var str = 'abcded';
var obj = new String(str);

function newToString() {
return 'hello world'
}

function func(val){
val.toString = newToString;
}

func(str);
console.log(str); // abcded

func(obj);
console.log(String(obj)) // hello world

func函数传入字符串时,是值传递,根据结果可知,不能直接修改其属性方法
传入obj对象时,是引用传递,传入后被修改了属性值,故打印出了hello world.

所以,到底是值传递还是引用,会根据传递的数据类型决定。

4.等值检测(==)运算规则

值类型与引用类型比较,将引用转换成值类型数据相同的数据,进行数据等值比较

两个值类型比较,将二者转换成相同数据类型,在进行数据等值比较

两个引用类型比较,仅比较引用地址

跟数字,布尔,字符串比较时
有任何一个是数字时,将另外一个转数字
有任何一个是布尔值时,转换成数字后比较
有任何一个是对象(或函数),调用valueOf方法来将其转换成值数据进行比较
按特定规则返回比较结果,如,undefined和null会返回true

等值检测一些特例

1
2
3
4
5
6
7
8
9
10
1. NaN不等于自身

NaN == (===/ != /!==) NaN // true

2. 符号可以转为true,但不等值于true

Boolean(Symbol()) ,!Symbol(), Symbol() ==(===) true // true false false

3. 即使字面量相同的引用类型,也是不严格相等的
{} === {}, /./ === /./, function() {} === function() {} // false false false

5.序列检测规则

两个值类型比较
直接比较数据在序列中的大小

值类型与引用类型进行比较
将引用类型转换成与值类型数据相同的的数据,再进行“序列大小”比较

两个引用类型进行比较
无意义,总是返回false

6.赋值
字符串赋值原理是复制地址,所以对于字符串的操作有了以下三种限制
a.不能直接修改字符串中的字符
b.字符串连接运算必然导致写复制,这将产生新的字符串
c.不能改变字符串的长度

7.函数隐式调用的几种情况
a.es6之后的模板处理函数
b.将函数用作属性读取器时,属性存取操作将隐世调用该函数,==>getter,setter
c.使用bind方法将原函数绑定为目标函数时,调用目标函数,就是隐世调用原函数
d.当使用Proxy() 创建原函数的代理对象时,调用代理对象也是隐式调用原函数
e.new运算符运算
f.当函数用作对象的符号属性,触发相应的行为时,就会隐式调用原函数

8.模块导入

1
2
3
4
5
import * as mynames from 'module'; // mynames.x是对象属性读取

import {x} from 'module'; // x是来自原模块的引用,得到的是引用值

let { x : x2 } = mynames // x2 是本地声明的变量可修改
  1. new 调用普通函数
    如果该普通函数返回对象,则返回该对象
    如果该函数返回值类型数据,则返回值忽略,返回this 引用,也就是普通函数本身

  2. arr[1,2,3] 会返回arr[3],中括号里面的1,2,3会形成逗号运算

  3. delete
    不能删除
    用var/let/const声明的变量与常量
    直接继承自原型的成员
    本质上用于删除对象自有属性表属性

  4. 关于对象
    所谓原型,就是构造器用于生成实例的模板

空白对象: 它的原型链上的所有自有属性表都为空
原型链:对象所有父类和祖先类的原型所形成的,可上溯访问的链表

函数和构造器并没有明显的界限,唯一区别只在于原型prototype是不是一个有意义的值

类是静态的声明,意味着类继承的构建过程也是静态的,是在语法分析期就决定了的

当在函数f中使用super.xxx时,无论该函数被用来作为那个实际对象的方法,
super都绑定在Object.getPrototypeOf(obj)上,f为obj的属性方法

在new 类的实例时,super()执行的目的就是回溯整个原型链,确保基类最先创建实例
没有在类中声明constructor()方法时,会默认添加并调用super()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function foo() {
var showInArrow = () => {
console.log(this.name);
}
showInArrow();
var obj = {
showInArrow,
name: 'aObject',
showThis: function() {
console.log(this.name)
}
}
with(obj) {
var showThis2 = () => { console.log(this.name)}
showThis();
showThis2();
showInArrow();
}
}
foo.call({name: 'outSide'})
==>
outSide
aObject
outSide
outSide

继承方式的选择上,

大型系统必须采用类继承的思路,继承关系的确定性和支持静态语法检测等特性
可以帮助开发者最终简化构建大型系统的开发和业务逻辑实现,并提供足够的系统稳定性

小型系统或者体系的局部使用原型继承的思路,既可以有优美的实现和高校的性质,
也可以更深入理解js中混合不同语言特性的精髓

1
2
3
4
5
6
function MyFunction() {}

MyFunction.prototype = new Function();
var myFunc = new MyFunction();

myFunc(); //触发异常无法执行

上面例子符合对象继承语义,但不能继承它的‘可执行’效果
内置对象的特殊效果不被对象系统继承
一方面这些效果被引擎绑定在特殊的构造器上,而不是他们的原型上
另一方面,系统只负责维护内部原型链,以确保instanceof运算能正确检测这种关系
而不负责这些特定效果的实现和传递

如果一个属性使用的是存取描述符,那么无论读写性质是什么,都不会新建属性描述符
子类中如果是继承来的这样的属性,那么在子类中对该属性的读写也会忠实地调用继承来的读写器

第四章

  1. 信息是对状态集合的解释,该集合的解释成本即是编程所应付的复杂性

编程的目的是使一个系统对外呈现可解释信息

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var x = 'outer', y = 'outer';
    function foo() {
    console.log(1, [x,y]) // [undefined, undefined]
    if(true) {
    function x() {}
    } else {
    function y() {}
    }
    console.log(2, [x,y]) // [f, undefined]
    }
    foo()

第五章

  1. 关于函数参数
    默认参数都是有名字的形式参数,但是从第一个参数开始,后续所有参数不会载计入形参个数
    剩余参数不计入形参个数
    模板参数参与计数,但是无论一个模板参数被解构为多少标识符,都按一个计算
    1
    2
    3
    4
    5
    6
    7
    8
    const ff = (a,b=1,c=2) => {console.log(34)}
    ff.length ==>1

    const ff2 = (a=3,b,c) => {console.log(34)}
    ff2.length ==>0

    const ff3 = (a, [b,c]) => {console.log(34)}
    ff3.length ==>2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo(filename) {
var [filename2, ...args] = arguments;

// filename 会影响arguments
filename = 'new file name';
console.log(arguments[0]); // new file name
console.log(filename2); // test.txt

// arguments 也会影响filename
arguments[0] = filename2;
console.log(filename);// test.txt

// 使用filenam2时,没有影响
filename2 = 'update again';
console.log(arguments[0]); // test.txt
console.log(filename);// test.txt
}
foo('test.txt');

arguments 获取的参数,不被赋予初始默认值

1
2
3
4
5
function foo(a=1,b,c=2,d) {
console.log(...arguments); // undefined,100,200,300
console.log(a,b,c,d); // 1,100,200,300
}
foo(undefined,100,200,300);

惰性求值

1
2
3
4
5
6
function foo(msg) {
console.log(msg)
}
var i = 100;
foo(i+=20, i*=2, 'value:'+i); // 120
foo(i); // 240

1
2
3
4
5
6
7
var f = function func2() {
console.log(typeof func2);
}

f() // function

console.log(typeof func2); // undefined

16.
类可以赋予对象成员,但是不能进行函数调用,可以用new 来调用生成实例

17.
绑定函数特殊性质

  1. 内部原型被置为与targetFunc的原型一致
  2. 没有自有的prototype属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MyFunc {
static foo() {
console.log('prototype method in myFunc');
}
}

class MyFuncEx extends MyFunc {
static foo() {
console.log('own method in MyFuncEX');
}
callMe() {
console.log('call me in MyFuncEx');
}
}

var f = MyFuncEx.bind();
MyFuncEx.foo(); // own method in MyFuncEX
f.foo(); // prototype method in myFunc

console.log('prototype' in Object.getPrototypeOf(MyFunc)) // false

// MyFunc.bind()生成的函数没有prototype
// class MyFuncEX2 extends MyFunc.bind() {} // 会报错

// MyFuncEx原型有继承自MyFunc的prototype,可以声明,但实例不会被继承
class MyFuncEx3 extends MyFuncEx.bind() {}

// 继续继承MyFuncEx原型的prototype属性,实例可继承
class MyFuncEx4 extends MyFuncEx {}
(new MyFuncEx4).callMe() //call me in MyFuncEx

console.log('callMe' in Object.getPrototypeOf(MyFuncEx4.prototype)); // true
console.log('callMe' in Object.getPrototypeOf(MyFuncEx3.prototype)); // false
console.log('callMe' in new MyFuncEx3); // false

18.
函数与对象的区别在于,前者内部结构中初始化了[[call]] 和 [[contruct]] 这两个内部方法
绑定函数通过这两个内部方法实现绑定逻辑
重写这两个方法并使其分别指向一段特有的调用或构建逻辑(以处理暂存在内部槽中的thisArg和arg1…n参数)
代理类对象Proxy则侧重重写了对象的全部13个内部方法
借助代理类也可以实现与绑定函数完全相同的功能
需要在handlers上添加自己的陷阱,以处理[[call]] 和 [[construc]]行为

19.
用属性来替代方法,并在递归中维护this引用

1
2
3
4
5
6
7
8
9
var obj = {
get fact() {
const fact = x=> x && x*fact(x-1) || this.power || 1;
return fact
}
}

obj.power = 100;
obj.fact(9) // 36288000

20.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var obj = {
[Symbol.iterator] : function*() {
var obj = {
[Symbol.iterator] : function*() {
for(var i =0;i< 10; i++) yield i;
}
}

console.log(...obj) // 0 1 2 3 4 5 6 7 8 9
}
}

console.log(...obj) // 0 1 2 3 4 5 6 7 8 9

var obj = {
start: 3,
[Symbol.iterator] : function*(start = 5, end = 10) {
var {start, end} = {start, end, ...this};
for(var i=start;i<end; i++) yield i;
}
}

console.log(...obj) // 3 4 5 6 7 8 9
obj.end = 6;
console.log(...obj) // 3 4 5

delete obj.end
delete obj.start
console.log(...obj) // 5 6 7 8 9

21.

1
2
3
4
5
var msg = (function myFunc(num) {
return myFunc = typeof myFunc;
})(10) + ", and upvalue's type is: " + typeof myFunc;

console.log(msg); // function, and upvalue's type is: undefined

第六章

  1. with语句传入的值如果是基础类型数据,会转换成相应类型对象再构建闭包
  2. null 作为对象总是转换为确定的三种值类型0,’null’和false
  3. 对于值类型来说,包装类上的toString()和valueOf方法其实只会对显示方法调用有效
    而并不影响原始值的运算
  4. 类型转换分为两个阶段,其一是转换为原始值,其二是转换为尝试运算的值类型
    这两个阶段,一个受上述”隐式转换逻辑”控制,另一个受具体的运算操作控制
    会尽可能的转换成预期的类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    x = {
    toString: () => '10',
    valueOf: () => -1
    }

    parseInt(x) ===> 10 因为规定参数是字符串类型

    Math.abs(x) ===> 1

    1 + x ===> 0

    "1" + x ===> 1

    delete x.valueOf

    1+x ===> '1-1'
1
2
3
var x = new Boolean(false)
console.log(x.valueOf()) ===> false
console.log(!!x) ===> false x此时是对象
  1. 一旦对象中声明过Symbol.toPrimitive属性,那么valueOf()与toString() 在值运算的隐式转换中就无效了
  2. switch() 语句对表达式求值,并用该值与case分支中的值进行比较运算时,会采用===操作符进行运算,优先进行类型检测而不会发生类型转换过程

  3. symbol只能转换成bool类型的true,尝试转换成其他值都会报错

  4. 补前缀并转大写

    1
    2
    3
    var x = 1234567

    x.toString(16).padStart(8, '0').toUpperCase() // 0012D687
  5. 数组当对象去结构时,只会保留有值的key

1
2
3
4
5
var arr = [1,2,'345',,12];
var { 0: x, 1:y, length} = arr;
console.log(x,y,length); // 1,2,5
var x = {length: 100, ...arr};
console.log(x.length + ' => ' + Object.keys(x)); // 100 => 0,1,2,4,length
  1. 计算一个字符串中不同字符个数
1
console.log(new Set('abcadf134oaafshjafgoi').size) ==> 14
  1. 是否可重写的限制主要是两个,可引用,可写

    1
    [100][0]++ // 100
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 检测成员是否是重写的
    var isRewrited = function(obj,key) {
    return obj.hasOwnProperty(key) && (key in Object.getPrototypeOf(obj));
    }

    // 检测成员是否是继承来的
    var isInherited = function(obj,key) {
    return (key in obj) && !obj.hasOwnProperty(key);
    }

    // 在对象及其原型链上查找属性的属主对象
    var getPropertyOwner = function f(obj,key) {
    return !obj ? null \
    : obj.hasOwnProperty(key) ? obj
    : f(Object.getPrototypeOf(obj),key)
    }
  3. 给Object.prototype添加成员,与添加全局变量名“效果相当”

1
2
3
4
Object.prototype.x = 100;
console.log(x) // 100
Object.getPrototypeOf(Object.getPrototypeOf(global)) === Object.prototype // true
global.constructor === Object
  1. try-catch-finally中的代码会在try最后return时,先被挂起,执行完finally再return
    如果return的值是个值类型则对返回值没有影响
    但如果是对象,return只保留引用,在finally中对return值进行了修改,那么将会影响返回值