模块化&数组&对象方法

密封

可以让对象密封,seal()不可新增/不可删除/可写

  1. var obj = { a: 2 };
  2. //对象的密封
  3. console.log(Object.seal(obj));
  4. //判断对象是否密封
  5. //false没有密封/true已开启密封
  6. console.log(Object.isSealed(obj)); //true

freeze()冻结对象,不可写/不可新增/可枚举/可读

  1. //密封对象级别最高的方式Object.freeze()
  2. var obj = { a: 2 };
  3. Object.freeze(obj);
  4. //false没有冻住/true冻住
  5. console.log(Object.isFrozen(obj));
  1. //深度冻结
  2. function myFreeze(obj) {
  3. Object.freeze(obj);
  4. for (var key in obj) {
  5. if (typeof (obj[key]) === 'object' && obj[key] !== null) {
  6. myFreeze(obj[key]);
  7. }
  8. }
  9. }

相等

判断是否严格相等的API中的Object.is()

  1. // ES5判断NaN === NaN
  2. console.log(NaN === NaN); //false
  3. console.log(+0 === -0); //true
  4. //ES6新API判断严格相等 sameValue
  5. console.log(Object.is(NaN, NaN)); //true
  6. console.log(Object.is(+0, -0)); //false

静态API

Array.of()

使用指定的参数作为定义数组里的数组元素

  1. //Array()方法创建的数组没办法控制数组里的数组元素
  2. console.log(Array()); //[]
  3. console.log(Array(3)); //[empty x 3]
  1. console.log(Array.of(1, 2, 3)); //[1, 2, 3]

Array.from()

将数组/类数组/部署迭代器接口的对象转化成真正的数组

  1. //将类数组转化成真正的数组
  2. function test() {
  3. console.log(arguments);
  4. //Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  5. console.log(Array.from(arguments));
  6. //[1, 2, 3]
  7. }
  8. test(1, 2, 3);
  1. //将部署迭代器接口的对象转化成真正的数组
  2. let obj = {
  3. start: [1, 3, 2, 4],
  4. end: [5, 7, 6],
  5. //中括号包裹字符串的方式
  6. [Symbol.iterator]() {
  7. //定义指针
  8. let index = 0,
  9. //组合新数组
  10. arr = [...this.start, ...this.end],
  11. //新数组长度
  12. len = arr.length;
  13. //将新数组进行迭代
  14. return {
  15. next() {
  16. if (index < len) {
  17. return {
  18. //累加的结果
  19. value: arr[index++],
  20. done: false
  21. }
  22. } else {
  23. return {
  24. value: undefined,
  25. done: true
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. console.log(Array.from(obj));
  33. //[1, 3, 2, 4, 5, 7, 6]

原型API

Array.prototype.fill()

填充修改原数组里的数组元素

  1. /**
  2. * fill(value, start, end);
  3. * @value 填充的值
  4. * @begin 从哪开始 包括
  5. * @end 从哪结束 不包括
  6. */
  7. console.log([1, 2, 3, 4].fill(5)); //[5, 5, 5, 5]
  8. console.log([1, 2, 3, 4].fill(5, 1)); //[1, 5, 5, 5]
  9. console.log([1, 2, 3, 4].fill(5, 1, 2)); //[1, 5, 3, 4]
  1. //对象也能用的情况
  2. console.log([].fill.call({
  3. length: 3
  4. }, 4));
  5. //{0: 4, 1: 4, 2: 4, length: 3}

Array.prototype.copyWithin()

指定区域复制到指定数组位置

  1. /**
  2. * 复制序列替换到指定位置
  3. * 注意:会修改原数组
  4. * arr.copyWithin(target, start, end)
  5. * @target 当前替换到哪的位置下标
  6. * @start 选出替换内容开始位置
  7. * @end 选出替换内容结束位置
  8. */
  9. console.log([1, 2, 3, 4, 5].copyWithin(2));
  10. //[1, 2, 1, 2, 3]
  11. console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(-2));
  12. //[1, 2, 3, 4, 5, 1, 2]
  13. console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3));
  14. //[4, 5, 6, 7, 5, 6, 7]
  15. console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3, 4));
  16. //[4, 2, 3, 4, 5, 6, 7]
  17. console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(-2, -3, -1));
  18. //[1, 2, 3, 4, 5, 5, 6]
  1. //特殊写法
  2. //并不好用的copyWithin
  3. console.log([].copyWithin.call({
  4. length: 5,
  5. 3: 1
  6. }, 0, 3));
  7. //{0: 1, 3: 1, length: 5}

Array.prototype.keys()

Array.prototype.values()

Array.prototype.entries()

遍历数组

  1. //打印的皆为遍历器对象,说明具有迭代器接口
  2. let arr = ['a', 'b', 'c'];
  3. console.log(arr.keys()); //Array Iterator {}
  4. console.log(arr.values()); //Array Iterator {}
  5. console.log(arr.entries()); //Array Iterator {}
  1. let arr = ['a', 'b', 'c'];
  2. var iter1 = arr.keys();
  3. console.log(iter1.next()); //{value: 0, done: false}
  4. console.log(iter1.next()); //{value: 1, done: false}
  5. console.log(iter1.next()); //{value: 2, done: false}
  6. console.log(iter1.next()); //{value: undefined, done: true}
  7. var iter2 = arr.values();
  8. console.log(iter2.next()); //{value: "a", done: false}
  9. console.log(iter2.next()); //{value: "b", done: false}
  10. console.log(iter2.next()); //{value: "c", done: false}
  11. console.log(iter2.next()); //{value: undefined, done: true}
  12. var iter3 = arr.entries();
  13. console.log(iter3.next()); //{value: [0, "a"], done: false}
  14. console.log(iter3.next()); //{value: [1, "b"], done: false}
  15. console.log(iter3.next()); //{value: [2, "c"], done: false}
  16. console.log(iter3.next()); //{value: undefined, done: true}

试着for of迭代

  1. var iter1 = arr.keys();
  2. var iter2 = arr.values();
  3. var iter3 = arr.entries();
  4. for (let i of iter1) {
  5. console.log(i); //0 1 2
  6. }
  7. for (let i of iter2) {
  8. console.log(i); //a b c
  9. }
  10. for (let i of iter3) {
  11. console.log(i); //[0, "a"] [1, "b"] [2, "c"]

Array.prototype.find()

Array.prototype.findIndex()

  1. /**
  2. * arr.find(function (elem,index,array) {})
  3. * @return 返回第一个符合指定规则的数组元素值
  4. */
  5. var arr = [1, 2, 3, 4];
  6. console.log(arr.find(function (value) {
  7. return value > 2;
  8. })); //3
  1. /**
  2. * arr.findIndex(function (elem,index,array) {})
  3. * @return 返回第一个符合指定规则的数组元素值的索引
  4. */
  5. var arr2 = [1, 2, 2, 4];
  6. console.log(arr.findIndex(function (value) {
  7. return value > 2;
  8. })); //2

解决indexOf的不足

  1. //找不到结果
  2. console.log([NaN].indexOf(NaN)); //-1
  3. //利用findIndex()解决
  4. //Object.is(NaN, val) 比对是否有NaN
  5. console.log([NaN].findIndex((val) => Object.is(NaN, val))); //0

Array.prototype.includes()

查看是否包含

  1. //判断数组是否包含该值
  2. console.log([1, 2, 3].includes(1)); //true
  3. console.log([1, 2, 3].includes(4)); //false
  4. console.log([NaN].includes(NaN)); //true

模块化

JavaScript在开发中遇到的问题

  • 依赖
  • 引用无用的代码
  • 变量覆盖重名污染全局
  • 加载顺序

早期解决方案:

闭包 -> 立即执行函数 -> 模块独立作用域 -> 解决各个模块之间的变量共享问题

关于Commonjs

Commonjs规范 -> 模块化规范 -> Nodejs

实际原理是引用的时候会创建新的模块实例,所有文件同步加载运行,对服务端友好,有缓存机制,只能在node上运行

关于AMD

来源于Commonjs,针对客户端用,只是表面像Commonjs

Asynchronous Module Definition异步模块定义

  • 定义模块 - define(moduleName,[module],factory)
  • 引入模块 - require([module, callback])

AMD语法

  1. define(模块名称, [依赖模块], 回调函数)
  2. define(moduleName, [module], factory)

需要配置路径

  1. require.config({
  2. path: {
  3. moduleA: 'js/module_a',
  4. moduleB: 'js/module_b',
  5. moduleC: 'js/module_c',
  6. }
  7. })

前置依赖: 这种写法需要前面的模块加载完毕才能执行后续程序

关于CMD阿里:

Common Module Definition 通用模块定义

  1. //定义模块
  2. define(function(require, exports, module){});
  1. //使用模块
  2. seajs.use([module路径], function(moduleA, moduleB, moduleC){});

为什么前端用es6而后端使用Commonjs

总结:

  • require加载
  • define定义
  • exports导出
  • module操作模块

CommonJS, AMD本质不同的是:

  • 依赖就近
  • 按需加载

关于ES6模块化:

Asynchronous Module Definition异步模块定义

  1. import module from '路径'; //导入模块
  1. export module; //导出模块

三者区别:

  1. commonjs模块输出的是一个值的拷贝, es6输出的是值的引用
  2. commonjs模块是在运行时加载,es6模块是在编译时加载

补充:模块化多个函数的变量会挂载到全局,通过对象命名空间的方式存储各个函数的变量减少全局变量的数量

  1. //模块化演变写法一:
  2. ;var module = (function(){
  3. function m1(){};
  4. function m2(){};
  5. var obj = {
  6. m1: m1,
  7. m2: m2
  8. }
  9. return {
  10. obj: obj
  11. }
  12. })();
  1. //模块化写法二之实例化构造函数
  2. //提供类的一部分功能
  3. //把相关性非常强的功能封装成一个类
  4. //用类的方式:主要是把相关性强的属性和方法封装在一起
  5. //通过立即执行函数的方式写出来的类,执行完可以利用垃圾回收销毁AO/GO,返回指定内容,保证变量的私有化

补充:宽放大模式

  1. //宽放大模式实现模块的注入
  2. //多个模块会存在相互之间依赖的问题
  3. //注入参数会实现变量的私有化
  4. var module = (function(){})(window.module || {});
  5. var module = (function(){})(module1, module2, module3...);

补充:命名空间,一个对象

requireJS仍有缺点:

局限性大,指定模块,指定依赖,不能处理更多更复杂模块化的依赖关系

  1. //模拟简化版AMD
  2. //实现方式:requireJS模块
  3. //calculator模块依赖math模块
  4. //参数m 依赖模块导出的参数
  5. /**
  6. * 执行namespace();
  7. * @参数name 模块名称
  8. * @参数deps 依赖模块
  9. * @参数fn 模块定义函数
  10. * @返回值 一个包含功能的方法的对象
  11. */
  12. //实现一个requireJS模块(具备功能依赖)
  13. var namespace = (function () {
  14. //模拟一个私有的对象包含指定的方法
  15. var cache = {};
  16. //创建模块
  17. return function createModule(name, deps, fn) {
  18. //递归的出口
  19. //情况为namespace(depsName);
  20. //返回模块化的名称
  21. if (arguments.length === 1) {
  22. return cache[name];
  23. }
  24. //情况2:有依赖的时候
  25. //遍历数组里所对应的依赖的方法['math','other',...]
  26. deps = deps.map(function (depsName) {
  27. //depsName为模块化之后拿到的当前的依赖
  28. return namespace(depsName);
  29. })
  30. //情况1:没有依赖的时候
  31. cache[name] = fn.apply(null, deps);
  32. //返回依赖对应的方法
  33. return cache[name];
  34. }
  35. })();
  36. //namespace(模块名称,依赖模块,回调函数)
  37. //定义一个模块
  38. var module1 = namespace('math', [], function () {
  39. function add(a, b) {
  40. return a + b;
  41. };
  42. function sub(a, b) {
  43. return a - b;
  44. };
  45. return {
  46. add,
  47. sub
  48. }
  49. });
  50. // console.log(module1.add(1, 2));
  51. var module2 = namespace('calculator', ['math'], function (m) {
  52. var action = 'add';
  53. function compute(a, b) {
  54. return m[action](a, b);
  55. }
  56. return {
  57. compute
  58. }
  59. })
  60. // console.log(module2.compute(3, 4));

插件化

插件化的开发方式:通过在全局对象上挂载属性,能够在外面通过<script>标签引入,在全局对象上new一个实例实现一部分功能的封装

插件/组件/模块化 区别:

  • 插件是一种开发的方式(技巧),一系列功能封装的构造函数,实例化后能够达到不同的对象,在不同的地方去使用,可复用
  • 组件是react(函数组件,类组件),vue(单文件组件),组件基于特定功能的UI,根据视图进行划分,拆分逻辑结构样式,包含入口和出口js文件
  • 模块化是特定的函数实现一个功能,多个函数累加在一起,功能集合,完成一系列功能的方法集合

导入导出

import/export是关键字,在ES6(es2015)标准下里的ES模块化(ES MODULE)

  1. 示例一:
  2. //utils.js
  3. export function test(){...}
  4. //main.js
  5. import {test} from './utils.js';
  6. test();
  7. 此时浏览器控制台报错:
  8. Uncaught SyntaxError: Cannot use import statement outside a module
  9. 不能够在一个不是module的文件里使用import关键字
  10. 然后给<script>加上type="module"
  11. 此时浏览器控制台再次报错:
  12. Access to script at 'file:///C:/frontEnd/es-module-exercise/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
  13. 跨域报错,只能在http/https服务器环境下才能支持模块加载
  14. 尝试通过vite启动服务器
  15. npm i -D vite@2.5.8
  16. 启动服务器
  17. 此时正常识别加载的模块并没有报错

import静态导入:

加载时编译的导入方式,初始化时加载模块

  1. import xxx from './xxx';
  2. //依赖`type="module"`

import动态导入:

  1. // 按需加载模块
  2. //不依赖`type="module"`
  3. import('./xxx');

注: 这里的import()并不是一个方法,而是import关键字 + ()

关于:

nomodule="true":运行在不支持ES2015标准的浏览器上,忽略type=module脚本

type="module": 运行在支持ES2015标准的浏览器上,忽略nomodule脚本

  1. //写法一:
  2. //utils.js
  3. export function plus(a, b) {
  4. return a + b;
  5. }
  6. export function minus(a, b) {
  7. return a - b;
  8. }
  9. //写法二:
  10. function plus(a, b) {
  11. return a + b;
  12. }
  13. function minus(a, b) {
  14. return a - b;
  15. }
  16. //注意:这里export导出的不是一个对象,而是一个模块类型!
  17. export {
  18. plus,
  19. minus
  20. }
  21. //写法一和写法二是同一个意思:都是导出导入一个模块而不是一个对象
  22. //main.js
  23. //导入时解构该模块:从一个模块类型里解构两个函数
  24. import {
  25. plus,
  26. minus
  27. } from './utils.js';
  28. console.log(plus(1, 2));
  29. console.log(minus(3, 4));

导入模块的几种写法:

  1. //可以通过*模块并命名为utils
  2. import * as utils from './utils';
  3. /**
  4. console.log(utils);
  5. * Module {
  6. * minus: ƒ minus(a, b),
  7. * plus: ƒ plus(a, b)
  8. * }
  9. */
  10. console.log(utils.plus(1, 2));
  11. console.log(utils.minus(3, 4));
  12. //也可以解构这个模块类型(像对象但不是一个对象)
  13. import { plus, minus } from './utils';
  14. console.log(plus(1, 2));
  15. console.log(minus(3, 4));
  16. //也可以别名
  17. import { plus as computePlus, minus } from './utils';
  18. console.log(computePlus(1, 2));
  19. console.log(minus(3, 4));
  20. //说明模块的每一个方法对应一个接口

导出模块的注意事项:

  1. //默认导出一个对象/命名空间
  2. //default表示导出的不是一个模块而是一个对象
  3. //将一个对象作为一个模块导出
  4. export default {
  5. plus,
  6. minus
  7. }
  8. //上面导出的方式影响导入时不能解构且报错
  9. import { plus, minus } from './utils';
  10. Uncaught SyntaxError: The requested module '/utils.js' does not provide an export named 'minus'
  11. //只能这样导入
  12. import utils from './utils';
  13. //console.log(utils); {plus: ƒ, minus: ƒ}
  14. //此时的utils是一个对象

副作用导入模块(模块内部有马上要执行的代码):

  1. //utils.js
  2. export function plus(a, b) {
  3. return a + b;
  4. }
  5. export function minus(a, b) {
  6. return a - b;
  7. }
  8. console.log('This is a UtilsModule');
  9. //main.js
  10. //模块一导入就会执行打印代码
  11. import './utils';

export defaultexport能否一起使用?

  1. //可以共存
  2. //utils.js
  3. export default {
  4. c: 3,
  5. d: 4
  6. }
  7. export function plus(a, b) {
  8. return a + b;
  9. }
  10. export function minus(a, b) {
  11. return a - b;
  12. }
  13. //main.js
  14. //注意:default默认必须先声明且放第一位
  15. import utils, {
  16. plus,
  17. minus
  18. } from './utils';
  19. //console.log(utils); {c: 3, d: 4}是一个对象
  20. console.log(plus(1, 2));
  21. console.log(minus(3, 4));

补:动态导入属于实验阶段

什么时候要使用动态import

  1. 静态导入太多的情况下,有一些不需要马上加载的模块可以使用动态导入
  2. 异步获取加载模块的方式
  3. 副作用加载import('./utils');
  4. 不能滥用动态导入
  1. //import('./utils')返回的是一个Promise对象,所以也是支持then的使用
  2. import('./utils').then(module => {
  3. // console.log(module);
  4. // Module {default: Object, minus: ƒ , plus: ƒ }
  5. });