0423-JS 笔记

一、var、let、const 区别与联系

JavaScript 是弱类型语言,var 可以定义各种数据类型和对象

var x = 0;
var x = 'abc';
var x = true;
var x = { name0: value0, name1: value1 };
var x = new Array('abc', 123, true); // var x = ["abc", 123, true]
var y; //undefined
var y = null; //null

var 可以重复声明并赋值同一变量,后者覆盖前者; 但 let 声明的变量在同一块级作用域中不能重复

var x = 0; //var 作用域是全局的或者函数级的
function foo() {
  let x = 1; //let 作用域是块级的{}内
  console.log(x);
}

console.log(x); // 0
foo(); // 1

循环体中,第一个例子循环本身及三次 timeout 回调均共享唯一的变量 i 。当循环结束执行时,i 的值变为 3,当第一个 timeout 执行时,调用的 i 值为 3 ;第二个人例子每次循环 let 声明的 i 均不一样,输出自然不一样。而的第二个才是我们想要的结果,所以循环体条件中很多情况用 let 更好

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
} // 输出 3 个 3

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
} // 输出 0,1,2

let 是更完美的 var

  • let 声明的变量拥有块级作用域
  • let 声明的全局变量不是全局对象的属性
  • 形如 for (let i…) 的循环在每次迭代时都为 i 创建新的绑定
  • 用 let 重定义变量会抛出一个语法错误(SyntaxError)

const 是 ES6 引入的新的声明类关键词,用来定义常量,不可改变常量的值

const MAX_CAT_SIZE_KG = 3000; // 正确

MAX_CAT_SIZE_KG = 5000; // 语法错误(SyntaxError)
MAX_CAT_SIZE_KG++; // 仍然会导致语法错误

二、循环

// for 循环
for (var i = 0; i < 9; i++) {
  console.log(i);
  // more statements
}

//while 循环
var n = 0;
var x = 0;

while (n < 3) {
  n++;
  x += n;
}

//do-while 循环

for…in 语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。

var obj = { a: 1, b: 2, c: 3 };
for (var prop in obj) {
  console.log('obj.' + prop + ' = ' + obj[prop]);
}

//obj.a = 1
//obj.b = 2
//obj.c = 3

var obj = { a: 1, b: 2, c: 3 };
for (var i in obj) {
  console.log(i);
}

//a
//b
//c

for…of 语句在可迭代对象 (包括 Array, Map, Set, String, TypedArray,arguments 对象等等) 上创建一个迭代循环,对每个不同属性的属性值, 调用一个自定义的有执行语句的迭代挂钩.

// 遍历 Array:
let iterable = [10, 20, 30];

for (let value of iterable) {
  console.log(value);
}
// 10
// 20
// 30

// 遍历 String:
let iterable = 'boo';

for (let value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"

// 遍历 TypedArray:
let iterable = new Uint8Array([0x00, 0xff]);

for (let value of iterable) {
  console.log(value);
}
// 0
// 255

// 遍历 Map:
// Map 对象就是简单的键 / 值映射。其中键和值可以是任意值 (对象或者原始值),键值不能重复
let iterable = new Map([['a', 1], ['b', 2], ['c', 3]]);

for (let entry of iterable) {
  console.log(entry);
}
// [a, 1]
// [b, 2]
// [c, 3]

for (let [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3

// 遍历 Set:
// 集合(Set)对象允许你存储任意类型的唯一值(不能重复),无论它是原始值或者是对象引用
let iterable = new Set([1, 1, 2, 2, 3, 3]);

for (let value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

// 遍历 DOM 集合...
// 遍历生成器...

// 遍历另外的可遍历对象:
// 您也可以遍历一个已经明确的可遍历(可迭代)协议。
// ES6 语法中 Symbol 是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用。Symbol 对象是一个 symbol primitive data type 的隐式对象包装器。
var iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (var value of iterable) {
  console.log(value);
}
// 0
// 1
// 2

for…of 与 for…in 的区别

  • for…in 循环会遍历一个 object 所有的可枚举属性。
  • for…of 语法是为各种 collection 对象专门定制的,并不适用于所有的 object. 它会以这种方式迭代出任何拥有 [Symbol.iterator] 属性的 collection 对象的每个元素。

下面的例子演示了 for…of 循环和 for…in 循环的区别。for…in 遍历(当前对象及其原型上的)每一个属性名称, 而 for…of 遍历(当前对象上的)每一个属性值:

Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

三、数组

Array 对象 -MDN

遍历数组:

普通方法遍历

// for 循环
for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

// for-of 循环
// 与 forEach() 不同的是,它可以正确响应 break、continue 和 return 语句
for (var value of myArray) {
  console.log(value);
}

forEach 遍历

对数组的遍历,参数是一个回调函数
['a', 'b', 'c'].forEach(function (x, i) { console.log(i+'.'+x) }) //x:数组中遍历到的每一个元素,i:相对应的下标

// 0.a
// 1.b
// 2.c

map 遍历

对数组的遍历,参数是一个回调函数,与 forEach 不同的是,map 函数返回一个数组

['a', 'b', 'c'].map(function(x, i) {
  return i + '.' + x;
}); //x:数组中遍历到的每一个元素,i:相对应的下标

// [ '0.a', '1.b', '2.c' ]

every 遍历

检查数组里的每一个元素的类型,参数是一个回调函数

['a', 'b']
  .every(function(x) {
    return typeof x === 'string';
  }) //x:数组中遍历到的每一个元素
  [
    // true

    ('a', 123)
  ].every(function(x) {
    return typeof x === 'string';
  });
// false

四、浮点型

IEEE 754
JavaScript 中的浮点数采用 IEEE-754 格式的规定。更具体的说是一个双精度格式,这意味着每个浮点数占 64 位。虽然它不是二进制表示浮点数的唯一途径,但它是目前最广泛使用的格式。该格式用 64 位二进制表示像下面这样:
image
你可能注意到机器表示的方法和约定俗成的书面表示一点不同。在 64 位中,1 位用于标志位——用来表示一个数是正数还是负数。11 位用于指数–这允许指数最大到 1024。剩下的 52 位代表的尾数。如果你曾经好奇为什么 JavaScript 中的某些东西如 +0 和 -0,标志位说明一切——JavaScript 中的所有数字都有符号位。Infinity 和 NaN 也被编码进浮点数——2047 作为一个特殊的指数。如果尾数是 0,它是一个正无穷或负无限。如果不是,那么它是 NaN。

JS 数字精度丢失的一些典型问题

解决方案:

/**
 * floatTool 包含加减乘除四个方法,能确保浮点数运算不丢失精度
 *
 * 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差),其根本原因是二进制和实现位数限制有些数无法有限表示
 * 以下是十进制小数对应的二进制表示
 *      0.1 >> 0.0001 1001 1001 1001…(1001 无限循环)
 *      0.2 >> 0.0011 0011 0011 0011…(0011 无限循环)
 * 计算机里每种数据类型的存储是一个有限宽度,比如 JavaScript 使用 64 位存储数字类型,因此超出的会舍去。舍去的部分就是精度丢失的部分。
 *
 * ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004)
 *  0.2 + 0.4 == 0.6000000000000001  (多了 0.0000000000001)
 *  19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatTool = (function() {
  /*
     * 判断 obj 是否为一个整数
     */
  function isInteger(obj) {
    return Math.floor(obj) === obj;
  }

  /*
     * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
     * @param floatNum {number} 小数
     * @return {object}
     *   {times:100, num: 314}
     */
  function toInteger(floatNum) {
    var ret = { times: 1, num: 0 };
    if (isInteger(floatNum)) {
      ret.num = floatNum;
      return ret;
    }
    var strfi = floatNum + '';
    var dotPos = strfi.indexOf('.');
    var len = strfi.substr(dotPos + 1).length;
    var times = Math.pow(10, len);
    var intNum = parseInt(floatNum * times + 0.5, 10);
    ret.times = times;
    ret.num = intNum;
    return ret;
  }

  /*
     * 核心方法,实现加减乘除运算,确保不丢失精度
     * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
     *
     * @param a {number} 运算数 1
     * @param b {number} 运算数 2
     * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
     * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
     *
     */
  function operation(a, b, op) {
    var o1 = toInteger(a);
    var o2 = toInteger(b);
    var n1 = o1.num;
    var n2 = o2.num;
    var t1 = o1.times;
    var t2 = o2.times;
    var max = t1 > t2 ? t1 : t2;
    var result = null;
    switch (op) {
      case 'add':
        if (t1 === t2) {
          // 两个小数位数相同
          result = n1 + n2;
        } else if (t1 > t2) {
          // o1 小数位 大于 o2
          result = n1 + n2 * (t1 / t2);
        } else {
          // o1 小数位 小于 o2
          result = n1 * (t2 / t1) + n2;
        }
        return result / max;
      case 'subtract':
        if (t1 === t2) {
          result = n1 - n2;
        } else if (t1 > t2) {
          result = n1 - n2 * (t1 / t2);
        } else {
          result = n1 * (t2 / t1) - n2;
        }
        return result / max;
      case 'multiply':
        result = (n1 * n2) / (t1 * t2);
        return result;
      case 'divide':
        return (result = (function() {
          var r1 = n1 / n2;
          var r2 = t2 / t1;
          return operation(r1, r2, 'multiply');
        })());
    }
  }

  // 加减乘除的四个接口
  function add(a, b) {
    return operation(a, b, 'add');
  }
  function subtract(a, b) {
    return operation(a, b, 'subtract');
  }
  function multiply(a, b) {
    return operation(a, b, 'multiply');
  }
  function divide(a, b) {
    return operation(a, b, 'divide');
  }

  // exports
  return {
    add: add,
    subtract: subtract,
    multiply: multiply,
    divide: divide
  };
})();
文章目录
  1. 一、var、let、const 区别与联系
    1. JavaScript 是弱类型语言,var 可以定义各种数据类型和对象
    2. var 可以重复声明并赋值同一变量,后者覆盖前者; 但 let 声明的变量在同一块级作用域中不能重复
    3. 循环体中,第一个例子循环本身及三次 timeout 回调均共享唯一的变量 i 。当循环结束执行时,i 的值变为 3,当第一个 timeout 执行时,调用的 i 值为 3 ;第二个人例子每次循环 let 声明的 i 均不一样,输出自然不一样。而的第二个才是我们想要的结果,所以循环体条件中很多情况用 let 更好
    4. let 是更完美的 var
    5. const 是 ES6 引入的新的声明类关键词,用来定义常量,不可改变常量的值
  2. 二、循环
    1. for…in 语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
    2. for…of 语句在可迭代对象 (包括 Array, Map, Set, String, TypedArray,arguments 对象等等) 上创建一个迭代循环,对每个不同属性的属性值, 调用一个自定义的有执行语句的迭代挂钩.
    3. for…of 与 for…in 的区别
  3. 三、数组
    1. 遍历数组:
  4. 四、浮点型
    1. 解决方案: