Netherspite

数据类型

  • 基本类型:Number Undefined Null Boolean String Symbol BigInt
  • 引用类型:Object RegExp Date Function Array
  • 基本类型按值访问,其值不可变,比较是值的比较,存放在栈
  • 引用类型按引用访问,值可变,可以用于属性和方法,存放在栈和堆,栈保存变量标识符和指向堆的指针,比较是引用的比较
  • .运算符提供了装箱操作,它会根据基本类型构造一个临时对象,使得能在基础类型上调用对应对象的方法。

null与undefined

  • null是空指针,undefined是变量未被初始化所赋予的值。没必要把变量赋值为undefinednull就可以。但null==undefined总是返回true
  • nullundefined没有toString方法
  • undefined是一个变量而不是关键字,为了避免无意中被篡改,有的编程规范会要求用void 0代替undefined。现代浏览器中undefined被设置成configurablewritablefalse

String

String有最大长度253-1,对字符串的操作charAtcharCodeAtlength等方法针对的都是UTF16编码,所以字符串的最大长度实际上是受字符串的编码长度影响的。0~65536(U+0000-U+FFFF)的码点被称为基本字符区域(BMP)。

Number

  • NaN占用了9007199254740990个数,这些原本是符合IEEE规则的数字。

  • JavaScript中有+0-0,可以用1/xInfinity还是-Infinity来区分

  • 非整数的Number类型正确的比较方法是使用JavaScript提供的最小精度值

    Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON();
  • Number整数类型的范围在-253-1~253-1之间,不含两个端点。Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER分别表示JavaScript中最大和最小的的安全整数:-253-1和253-1

类型转换

原值 Boolean Number String Object
Undefined undefined false NaN "undefined"
Null null false 0 "null"
Boolean true 1 "true" new Boolean(true)
false 0 "false" new Boolean(false)
String "" false 0 new String("")
"123" true 123 new String("123")
"1a" treu NaN new String("1a")
Number 0 false "0" new Number(0)
1 true "1" new Number(1)
  • 字符串到数字的类型转换支持十进制、二进制、八进制、十六进制以及正负号科学计数法:
    • 30
    • 0b111
    • 0o13
    • 0xFF
    • 1e3
    • -1e-2
  • Number较大或较小时字符串会用科学计数法表示
  • ToPrimitive函数式对象类型到基本类型的拆箱转换,对象到StringNumber的转换会先将对象变成基本类型,再从基本类型转换为对应的StringNumber。拆箱转换会尝试调用valueOftoString来获得拆箱后的基本类型,没有这两个函数或者没有返回基本类型会产生TypeErrorSymbol.toPrimitive可以覆盖原有的行为。一般情况下没有指定转换类型时,都会优先调用valueOf,若返回的不是基本类型,则调用toString

数据类型判断

  • typeof
    一元运算符,放在一个运算符之前,返回为一个字符串,其中基本类型的null返回为object对象,其他都返回基本类型,函数返回function。缺点:不能检测数组和对象

  • instanceof
    可以判断一个变量是否是某个对象的实例,用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。a instanceof Array
    缺点:

    • 对基本数据类型,字面量方式创建出来的结果和实例方式创建有一定区别
      console.log(1 instanceof Number)//false
      console.log(new Number(1) instanceof Number)//true
    • 只要在当前实例的原型链上,用其检测出来的结果都是true。在类的原型继承中,检测出的结果未必准确
    • 不能检测nullundefined
  • constructor
    返回对创建此对象的数组函数的引用,与instanceof相似,还可以处理基本数据类型的检测。a.constructor == Array
    缺点:

    • 不能检测nullundefined
    • 函数的constructor是不稳定的,类的原型重写时可能会把之前的constructor覆盖
      function Fn(){}
      Fn.prototype = new Array()
      var f = new Fn
      console.log(f.constructor)//Array
  • Object.prototype.toString.call

    function isArray(o) { 
    return Object.prototype.toString.call(o) === '[object Array]';
    }

    Object.prototype.toString() 获取对象的类名然后将[object 类名]组合并返回,既解决了instanceof存在的跨页面问题,也解决了属性检测方式所存在的问题

  • ES6新增Array.isArray()

  • 特性判断

    function isArray(object){
    return object && typeof object==='object' &&
    typeof object.length==='number' &&
    typeof object.splice==='function' &&
    //判断length属性是否是可枚举的 对于数组 将得到false
    !(object.propertyIsEnumerable('length'));
    }

    lengthsplice不一定是数组,但是length不能枚举便可以判断其不是对象的属性
    object.propertyIsEnumerable(proName)判断指定的属性是否可列举

数据类型相等判断

var toString = Object.prototype.toString;
function isFunction(obj) {
return toString.call(obj) === '[object Function]'
}
function eq(a, b, aStack, bStack) {
/*a === b的结果为true,但1 / +0(Infinity) !== 1 / -0(-Infinity)。由于JavaScript采用浮点数表示法,最高位是符号位,才有了正负零。*/
if (a === b) return a !== 0 || 1 / a === 1 / b;
// typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
if (a == null || b == null) return false;
// 判断 NaN,利用NaN不等于自身的特性
if (a !== a) return b !== b;
// 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
// 更复杂的对象使用 deepEq 函数进行深度比较
return deepEq(a, b, aStack, bStack);
};
function deepEq(a, b, aStack, bStack) {
// a 和 b 的内部属性 [[class]] 相同时 返回 true
var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {
case '[object RegExp]': case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]': case '[object Boolean]':
return +a === +b;
}
var areArrays = className === '[object Array]';
// 判断构造函数实例
if (!areArrays) {
// 过滤掉两个函数的情况
if (typeof a != 'object' || typeof b != 'object') return false;
var aCtor = a.constructor, bCtor = b.constructor;
//aCtor和bCtor必须都存在并且都不是Object构造函数的情况下,aCtor不等于bCtor,那这两个对象就真的不相等啦
if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
return false;
}
}
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
// 检查是否有循环引用的部分
while (length--) {
if (aStack[length] === a) {
return bStack[length] === b;
}
}
aStack.push(a);
bStack.push(b);
// 数组判断
if (areArrays) {
length = a.length;
if (length !== b.length) return false;
while (length--) {
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
} else {
// 对象判断
var keys = Object.keys(a), key;
length = keys.length;
if (Object.keys(b).length !== length) return false;
while (length--) {
key = keys[length];
if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
aStack.pop();
bStack.pop();
return true;
}

数组方法

  • map:将调用的数组的每个元素传递给指定的函数,并返回一个数组

    const arr = [1, 2, 3, 4, 5, 6];
    const mapped = arr.map(el => el + 20);
    console.log(mapped);// [21, 22, 23, 24, 25, 26]
  • filter:返回的数组元素是函数逻辑为真的一个子集。

    const arr = [1, 2, 3, 4, 5, 6];
    const filtered = arr.filter(el => el === 2 || el === 4);
    console.log(filtered);// [2, 4]
  • reduce:按函数方法计算值。

    const arr = [1, 2, 3, 4, 5, 6];
    const reduced = arr.reduce((total, current) => total + current);
    console.log(reduced);// 21
  • find, findIndex, indexOf, push, pop

  • shift:从数组头部删除一个元素,它改变了原数组。函数本身返回被删除元素的值

    let arr = [1, 2, 3, 4];
    const shifted = arr.shift();
    console.log(arr);// [2, 3, 4]
    console.log(shifted);// 1
  • unshift:在数组头≠部添加一个或多个元素,它改变了原数组。与其他很多方法不同的是,函数本身返回数组新的长度

    let arr = [1, 2, 3, 4];
    const unshifted = arr.unshift(5, 6, 7);
    console.log(arr);// [5, 6, 7, 1, 2, 3, 4]
    console.log(unshifted);// 7

call和apply

  • 相同点:作用相同,第一个参数都为执行时的上下文对象

  • 不同点:接收参数的方式不同,对于call,传递给函数的参数必须逐个列举出来;对于apply时,传递给函数的是参数数组。例子如下:

     function add(c, d){ 
    return this.a + this.b + c + d;
    }
    var o = {a:1, b:3};
    add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
    add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

    深拷贝和浅拷贝

  • JSON.stringifyJSON.parse可以实现,但是会丢失undefinedfunctionsymbol类型的属性

  • 数组的sliceconcat可以实现一级属性的拷贝,但是二级及以上的属性不能深拷贝

  • 递归函数实现

多个节点插入DOM

多个节点插入DOM时,由于渲染回流,在for循环内部多次appendChild会造成多次渲染,从而出现卡、闪屏的现象。javascript提供了一个文档片段DocumentFragment的机制。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点。把所有要构造的节点都放在文档片段中执行,这样可以不影响文档树,也就不会造成页面渲染。当节点都构造完成后,再将文档片段对象添加到页面中,这时所有的节点都会一次性渲染出来,这样就能减少浏览器负担,提高页面渲染速度。

var fragment = document.createDocumentFragment();

参考文章

  1. JavaScript类型:关于类型,有哪些你不知道的细节?
  2. JavaScript对象:面向对象还是基于对象?
  3. Number

 评论