JS 语言精粹 - 记

90 阅读8分钟

核心

image.png image.png

  • 简单类型不可变
  • typeof 错误
作用域
变量名哈希散列( 需要的大小/堆范围 )
aNumber01001234
aString0110abcd
aObject0000d92hdh839ehfh11
aArray0001d92hdh839ehfh22
anotherArray0001d92hdh839ehfh22
aNull00000000000000000000000000
内存堆
aObjectaArrayanotherArray
d92hdh839ehfh33
  • 内存块满了怎么办? - 分配新的内存块,并迁移
对象栈
属性名哈希散列( 需要的大小/堆范围 )
aNumber11234
aString2abcd
aObject0000d92hdh839ehfh11
aArray0001d92hdh839ehfh22
aNull0000d92hdh839ehfh22
proto0000d92hdh839ehfh55
数组 - 对象栈
变量名哈希散列( 需要的大小/堆范围 )
011234
12abcd
20000d92hdh839ehfh11
NAME0001d92hdh839ehfh22
proto0000d92hdh839ehfh55

语法

数字

数字有 int float double,整型 浮点 双精度浮点。但 JS 不管。

NaN 是一个甚至不等于自己的数字,用于表示你写错了。

JS 内置对象 Math,用于数学运算。

字符串

js 所有字符都是 16 位的。

字符串是不可变的( number 和 boolean 也可以这样说,因为在栈内存)。

字符串拥有一些方法。

语句

每个基础类型都有被当作假的值,number 的 NaN 让数字类型拥有两个。

类型比较,是比较栈内存值。

for in

112345
name'name'
father->

循环一个对象的所有属性,包括原型。

创建一个迭代器,使用迭代器来遍历。

迭代器创建一个指针,指向目标的地址(也是起始地址),再每次 next 时向后移动。直到指针指向了目标之外(如何判断到了目标外)。

例如对于数组和对象,也就是复杂类型,他们是哈希型数据结构,指针指向键地址,读取值。也是因为是哈希类型,所以对于对象来说,属性的顺序是不固定的(es6 之前,因为有些引擎迭代是迭代值地址,不是key值)。

在 ECMAScript 2015 (ES6) 规范之前,JavaScript 对象的属性遍历顺序并没有被规范明确定义。不同的 JavaScript 引擎或不同的运行环境(浏览器、Node.js 等)可能会以不同的方式对对象属性进行内部实现,导致属性的遍历顺序不一致。

从 ECMAScript 2015 (ES6) 开始,规范明确了对象属性的遍历顺序应该与属性添加的顺序保持一致。也就是说,使用 for...in 循环或 Object.keys() 方法遍历对象的属性时,它们会按照属性添加的顺序进行遍历,最后遍历原型链。但需要注意的是,这仅适用于普通的数字和字符串属性,而不包括特殊类型的属性(如 Symbol 属性)或属性访问器。

数组一致是类似遍历对象从一个新的储存key值对数组中取key。

哈希的散列化,将一个任意大小的值散列化为一个固定长度的序列,输入窗口大于输出窗口,导致散列可能重复。在 JS 中使用链表法解决,当散列值作为地址,发现地址已经被使用,则在当前储存的(key,value,next),的 next ,以地址来讲,就是紧贴着当前空间进行存储。

对象

对象是可变的键值对,树状,叶子结点是简单类型。

字面量,理解为一个特定的样子,如果见到这个样子,js 会把他创建为一个对应的类型,像是一个“位置函数”。

就像[ a ] => Array(a)

原型,像是一个特殊的键,这个键的值会在使用时当作当前对象的,像是拍平了一层,而她的原型又有原型。。。知道原型的终点 null。

对key值的检索可以obj.['key'] / obj.key;

检索则是在自身和原型上找到键值,获取值,或者地址。

原型 prototype

hasOwnProperty 检查是否在本身而非原型上

函数

函数也是对象,但是多了两个属性,上下文与行为代码。

函数除了定义的参数,也隐藏式接受了 this 和 arguments 参数,也就是常说的谁调用 this 就是谁;arguments 或者说其实函数只接受了这个 arguments,而定义的参数是一个语法糖,帮你从中拿出。

arguments 是一个四不像,有 lenth ,像是数组的索引,但并没有数组的方法。它的原型是对象的原型。

函数总会返回,用 return 手动,自动 undefined,new 关键词则返回 this。

(箭头函数不管,但箭头函数原型和正常函数是一样的)

函数直接调用时,this 会取到全局,这是个bug,预期取到调用处的上下文。

箭头函数的底层原理?

块作用域与const let;

闭包

暴露上下文变量。

回收不了垃圾。

柯里化,高阶函数?

闭包的作用被块级作用域挤压的没啥用了感觉。

闭包很大一个作用是隐藏变量,但总要有一个出口,把这些变量挂在在出口属性上呗。。。

继承

一句话表达你对原型的理解:实例继承—— 不对

function myNew(Creater,...args){
    //判断
    if (typeof Creater !== 'function') return new Error('type err');
    //生成新对象并继承
    let obj = {};
    let res = Creater.call(obj,...args);
    obj.__proto__ = Object.create(Creater.prototype);
    // Object.setPrototypeOf(obj,Creater.prototype);官方MDN表示性能差不推荐
    obj.constructor = Creater;
    //判断函数本身的返回是否为引用类型
    let isObject = typeof res === 'object' && res !== null;
    let isFunction = typeof res === 'function';
    return (isFunction || isObject)? res:obj;
}

私有变量

使用闭包,函数浅拷贝一个值返回,来达到只读。

原型链继承

function A() {
   this.info = {
       name: "yhd",
       age: 18,
   };
  this.getInfo = {
    return this.info;
  }

}

直接让子类原型指向父类实例。

let b = new B();
B.protoType = new A();

问题:引用类型共用

构造函数继承

在子类构造函数中使用父类构造函数强化。

//解决了引用共用问题
function B(){
  A.call(this);
}

问题
每个子类都继承于一个独立的父类实例,大量内存浪费
所有属性方法写在构造函数中无法复用
不能跨节点继承

没有公共原型实例

组合继承

构造函数继承A实例并重写构造方法改回自己。
构造函数偷用A进行实例加强。

function B (){
  A.call(this);
}
B.prototype = new A();
B.constructor = B;

问题
原型有父类实例,自己又使用父类构造函数强化,父类属性与方法覆盖重合。
但是可以跨节点继承了,可以操作原型添加方法了,可以私有引用类型了。

组合寄生式继承

使用组合继承但是原型指向操作跨过父类构造直接指向父类的原型。

function B(){
  A.call(this);
}
function myExtend (son,father){
    let proto = Object.create(father.prototype);
    proto.constructor = son;
    son.prototype = proto;
}
myExtend(B,A);

最成熟的方法。

数组

得益于对象继承,数组的每个值也是键值对,所以值可以是任何类型,或者说 js 根本没有数组,只是长得像其他语言数组的一个对象。

数组其实是数字string作为key

同理 delete 会删除这个 key

判断数组

方法

介绍了一堆 api

代码风格

写这本书的时候没有 Prettier 吗

  • 缩进
  • 注释
  • if 的花括号等

优美特性

  • 动态对象,基于原型继承
  • 字面量定义
  • 函数词法作用域 / 块作用域

垃圾

  • 原来变量定义都写在上面是这个原因:没有块级作用域的变量提升。
  • 大量保留字
  • UniCode 认为一对字符是两个不同的字符 —— 没看懂
  • typeof null 是 object

typeof 用于检测给定数据类型的 typeTag,枚举有以下:

  • "undefined":表示未定义的值。
  • "boolean":表示布尔值。
  • "number":表示数字。
  • "string":表示字符串。
  • "symbol":表示符号。
  • "function":表示函数。
  • "object":表示对象(包括对象、数组、null 等)
    • 关键词同时负责连接字符串 & 加法运算,一边是字符串则认为另一边也作为字符串。
  • 0.1 + 0.2 不是 0.3
  • NaN 不是常量 !?

  • hasOwnProperty 不是运算符,使用 call/apply?

call / apply:判断第一个参数是否为对象,不是则使用window,使用一个 Symbol 在对象上绑定 this 函数,调用获得结果,再删掉绑定。

bind 则第一个参数不是对象时报错,返回一个等待调用的 apply,返回前手动将结果的原型设为第一个参数的原型。其中 apply 的第一个参数,考虑结果可能作为构造函数使用new调用,所以判断this 的原型是否是为结果函数本身,如果是则不绑定obj转而绑定新的对象this。

  • 使用 hasownProperty 防止用字符串去值是字符串与原型方法相撞

榴莲,好吃但扎手

  • 双等号,不推荐用,不会我也不学,主打一个拒绝。
  • with ,没听说过呢,看起来是“优先使用”的意思
  • eval 还是有立功的,各种在线测试网站
  • 少一个花括号的单行语句,好不好吃不一定,肯定扎手
  • 居然不让用自增自减?
  • 位运算符,之前跟行见学过,忘了,js 的位运算并不快,但位运算可以补个课。//TODO
  • Boolean 等,书说很奇怪的存在。但我常用于类型转换
  • 没啥用的 void,是一个返回undefined的壳子,不向C表示无值,但在编译器中会按照无值类型解释。

115