JS简记

101 阅读6分钟
1、currentTarget 和 target
  • target在事件流的目标阶段,指被点击的对象;
  • currentTarget在事件流的捕获、目标及冒泡阶段,指向事件绑定的元素(一般为父级元素)

只有当事件流处在目标阶段的时候,两个的指向才是一样的。

2. 模块化

CommonJS

# commonjs是什么_深入理解CommonJS

Node 会把整个待加载的 .js文件放入一个包装函数load中执行。在执行这个load()函数前,Node事先准备好了module对象. module中有一个空的exports变量。

var load = function (exports, module) {
    // .js的文件内容
    ...
    // load函数返回:
    return module.exports;
};

var exported = load(module.exports, module);
  • commonJS 用同步的方式加载模块
    • 系统模块
    • 自定义模块
    • 第三方模块
  • module.exports
    • module.exports = {} 更改指向后也没关系,因为获取数据是根据变量来的
  • exports
    • 初始时与 module.exports 指向同一块内存地址
    • 在使用 module.exports = {} 或 exports = {} 后会更改指向
  • require
    • 导入的是 module.exports 对象
//1.js
module.exports.name = 'Tom'
exports.age = 18
console.log(module.exports) //{ name: 'Tom', age: 18 }
console.log(exports)  //{ name: 'Tom', age: 18 }
//2.js
module.exports = { name: 'Tom' }
exports.age = 18
console.log(module.exports) //{ name: 'Tom' }
console.log(exports)  //{ age: 18 }
const x = require('./2');
console.log(x);  //{ name: 'Tom' }

require查找规则# 45.CommonJS详解

nodejs.org/dist/latest…

  1. 导入一个 Node 核心模块,比如:path、http
    • 直接返回核心模块,并且停止查找
  2. 是以 ./ 或 ../ 或 / 开头的
    • 将路径当做一个文件在对应的目录下查找
      • 如果有后缀名,按照后缀名的格式查找对应的文件
      • 如果没有后缀名,会按照如下顺序:
        • ①、直接查找同名文件
        • ②、查找 .js 文件
        • ③、查找 .json 文件
        • ④、查找 .node 文件
    • 没有找到对应的文件,将路径作为一个目录
      • 查找目录下面的 index 文件
        • ①、查找 x/index.js 文件
        • ②、查找 x/index.json 文件
        • ③、查找 x/index.node 文件
    • 如果没有找到,那么报错:not found
  3. 不是核心模块,也不是一个路径
    • 会先在当前文件所在目录下的 node_modules 目录下寻找
    • 如果没有找到,再去上级目录下的 node_modules 目录下寻找
    • 如果依然没找到,继续去上上级目录下的 node_modules 目录下寻找
    • 直至找到根目录,依然没有找到,则报错 not found

AMD

  • AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行
  • 使用 require.js 的步骤
    • define() 定义模块
    • require.config() 指定引用路径等
    • require() 加载模块。

# Javascript模块化编程(三):require.js的用法

# requirejs.org

//math.js
// 定义模块
define(function() {
    return { add: function(x, y){ return x + y; } }
})
// 定义模块。依赖underscore模块,将模块放在[]内
define(['underscore'], function(_) {
    return { reduce: function(){} }; 
}) 
    
// 引用模块。执行业务代码
require(['jquery', 'math'], function($, math) { 
    var sum = math.add(10,20);
    $("#sum").html(sum);
});
//main.js(主模块)
// config()指定各模块路径和引用名
require.config({
    baseUrl: "js/lib",
    paths: {
        "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
        "underscore": "underscore.min",
    }
});
// 引用模块。执行业务代码
require(["jquery", "underscore", "math"], function($, _, math) {
  math.add(1, 2)
});
<script src="js/require.js" data-main="js/main"></script>

CMD

  • AMD 推崇依赖前置、提前执行;CMD推崇依赖就近、延迟执行

ES6 Module

编译时加载的好处:

  • 可以对模块进行静态分析(宏、类型检测)
  • 不需要 UMD 模块规范了

关键字

  • export
  • export default
  • import
    • import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
  • as
//1.js
function a() {}

// export a;   //①、❎ Declaration or statement expected. 需要是一个声明或表达式
// export const b = 10 //②、✅
// export { a };       //③、✅
// export { a as c }

export default a;   //④、✅
//export default { a };  //⑤、✅
//2.js
import a from './1';
console.log('a =', a) // ②③、a = undefined
console.log('a =', a) // ④、a = function{}
console.log('a =', a) // ⑤、a = Object({ function{} })

ES6的模块不是对象。无法用变量 a 接收到对象,而是在编译阶段对模块导出数据进行同名赋值。

# SyntaxError: Cannot use import statement outside a module

ES6 模块与 CommonJS 模块的差异

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
    • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    • 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
    • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

3. 加法精度问题

image.png

image.png

使用 antd 的 InputNumber 组件时,动态设置 minmax 属性,因为加法的精度问题,导致数据越界。

解决:

# js 0.1+0.2不等于0.3的解决办法

<InputNumber
    min={minValue ? (minValue * 100 + 1) / 100 : ((maxValue || 0) * 100 + 1) / 100}
    max={1000}
    precision={2}
    step={0.01}
    style={{width: 150}}
/>
4. Array().fill({})

注意:数组的每个元素指向的是相同的对象

let arr1 = new Array(3).fill({ a: 0 });
arr1[1].a = 1;
console.log(arr1);  // [{a: 1}1, {a: 1}, {a: 1}]

let arr2 = new Array({ a: 0}, { a: 0 }, { a: 0});
arr2[1].a = 1;
console.log(arr2); // [{a: 0}, {a: 1}, {a: 0}]
5. 删除对象/数组中 null、undefined

# js删除对象/数组中null、undefined、空对象及空数组方法示例

  • 方案①:递归处理
export const delEmptyQueryNodes = (obj = {}) => {
    Object.keys(obj).forEach((key) => {
        let value = obj[key];
        //递归
        value && typeof value === 'object' && delEmptyQueryNodes(value);
        (value === null || value === undefined) && delete obj[key];
    });
    return obj;
};
  • 方案②:借助数组的方法(以下代码未处理多层级)
// filter 过滤有数据的 key;reduce 可以换成 forEach 等遍历的方法
const params = Object.keys(data)
	.filter((key) => data[key] !== null && data[key] !== undefined)
	.reduce((acc, key) => ({ ...acc, [key]: data[key] }), {});
6. 字符串转换为数字
/** 浮点数字符串 */
parseInt('1.3')   //1
Number('1.3')     //1.3
+'1.3'            //1.3
parseFloat('1.3') //1.3
Math.floor('1.3') //1
~~'1.3'           //1
'1.3' * 1         //1.3

/** 中间包含字母 */
parseInt('1.f3')   //1
Number('1.f3')     //NaN
+'1.f3'            //NaN
parseFloat('1.f3') //1
Math.floor('1.f3') //NaN
~~'1.f3'           //0
'1.f3' * 1         //NaN

/** 头部包含字母 */
parseInt('1.f3')   //NaN
Number('1.f3')     //NaN
+'1.f3'            //NaN
parseFloat('1.f3') //NaN
Math.floor('1.f3') //NaN
~~'1.f3'           //0
'1.f3' * 1         //NaN