问题背景
某angular4项目接入神策sdk失败,控制台报错undefined
引入方法
import sa from 'sa-sdk-javascript';
sa.init({
server_url: 'https://xxx.com';
});
最终解决方案
import * as sa from 'sa-sdk-javascript';
sa.init({
server_url: 'https://xxx.com';
});
这两种引入方式有什么区别呢,来看一个例子
exporter.js
export default {
one: "One",
two: "Two",
three: "Three",
};
export var a = 'a';
importer.js
// Has 3 props ['one', 'two', 'three']
import anyname from './exporter';
// Has 2 props ['default', 'a'].
import * as allExports from './exporter';
import {one} from './exporter';
import two from './exporter';
console.log(anyname);
console.log(allExports);
console.log(one);
console.log(two);
输出如下
{one: "One", two: "Two", three: "Three"}
one: "One"
three: "Three"
two: "Two"
__proto__: Object
a: (...)
default: {one: "One", two: "Two", three: "Three"}
Symbol(Symbol.toStringTag): "Module"
__esModule: true
get a: ƒ ()
__proto__: Object
undefined
{one: "One", two: "Two", three: "Three"}
one: "One"
three: "Three"
two: "Two"
__proto__: Object
这是成功的例子,还有在此之前失败的例子
importer.js
// Has 3 props ['one', 'two', 'three']
import defaultExport from './exporter';
// Has 2 props ['default', 'a'].
import * as allExports from 'exporter';
console.log(defaultExport)
console.log(allExports)
直接运行node importer.js
import defaultExport from './exporter';
^^^^^^^^^^^^^
SyntaxError: Unexpected identifier
at new Script (vm.js:74:7)
at createScript (vm.js:246:10)
at Object.runInThisContext (vm.js:298:10)
at Module._compile (internal/modules/cjs/loader.js:646:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
at Module.load (internal/modules/cjs/loader.js:589:32)
at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
at Function.Module._load (internal/modules/cjs/loader.js:520:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
at startup (internal/bootstrap/node.js:228:19)
成功运行是因为什么呢,是放在angular项目中运行的,关键在于babel
至于import
和export
的理解呢,也不深,学习一下es6中Module 的语法
以下是关键词笔记
一、历史上js
没有模块(module)体系
也就是
js
最初的设计根本没有模块
这个概念
缺陷导致:无法将大的程序拆分成相互依赖的小文件(模块),再用简单的方法拼装起来
其实这是对于工程上的缺陷,之所以要把大的程序拆分成相互依赖的小模块,是为了不同的功能可以分开进行开发和维护,就像一个个小系统一样,而最终要用简单的方式拼装起来是因为程序运行并不关注程序究竟有多大有多复杂,只要程序运行的通就能跑起来,分治策略只是人类帮组自己理解和处理庞大程序的手段。
没有对比就没有伤害:同为脚本语言的ruby有自己的require,python有import,连样式语言css都有@import,es6之前的js被比的无地自容
二、CommonJS
和AMD
原来是在es6出现前的社区
解决方案
CommonJS
用于服务器,AMD
用于浏览器
三、es6从语言标准
层面上实现了模块功能,而且实现得相当简单
nice,从此昂起头颅
es6的模块功能可以取代CommonJS
和AMD
,成为通用
的模块解决方案
四、es6模块设计思想是尽量的静态化
(又一个新概念)
静态化是指编译时
就能确定模块的依赖关系
以及输入
和输出
的变量
五、对比CommonJS
的优势比如CommonJS
的模块是对象,输入时必须查找对象属性
就很僵硬
看到这里我觉得有必要再去深入了解一下历史,也就是CommonJS
和AMD
是怎么一回事
六、关于静态化的CommonJS
和es6模块的对比
CommonJS
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
第一行虽然看起来和我们现在用的es6
模块用法相仿,但实际上有两点关键的不同
- 上述操作实际
整体加载
了fs
模块,而只是仅仅用到了里面的三个方法 - 上述动作是在程序
运行时加载
的,因为只有运行时才能得到这个对象(因此无法在编译阶段做静态优化
)
es6模块
// ES6模块
import { stat, exists, readFile } from 'fs';
- es6模块不是对象,而是通过
export
命令显式
指定输出的代码,再通过import
命令输入 - 上述操作只会加载这3个方法,其他方法不加载,称为
编译时加载(静态加载)
这就是从语言标准层面实现的优势(一雪前耻),简简单单增加两个内置关键词,通过命令就完成了编译前的模块传输声明,社区方案只能曲线救国。
七、关于export
命令
- 一个模块就是一个独立的文件
- 文件内部所有变量外部无法获取
- 外部读取模块内变量必须使用
export
输出该变量
例如
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
另一种写法
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
输出方法或类
export function multiply(x, y) {
return x * y;
};
export class multiply(x, y) {
return x * y;
};
输出时重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion // 同一个输出对象(变量、类、方法)可以以不同的重命名输出多次
};
注意事项
export
命令规定的是对外的接口,必须与模块内的变量建立一对一关系(接口名与模块内部变量一对一)- 不能直接对外输出变量的值,而是应以接口的形式输出
反例
// 直接输出值
export 1;
var m = 1;
// 直接输出变量的值
export m;
正解
// 直接输出声明的变量
export var m = 1;
// 先声明变量,再输出变量
var m = 1;
export {m};
// 先声明变量,再将变量以别名输出
var n = 1;
export {n as m};
以上三种都是在对外输出接口m
,其他脚本通过接口m
获取到其内部的值
特点
export
语句输出的接口与其对应的值是动态绑定
关系,通过该接口可以取到模块内部实时
的值
例如
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
foo的值先是bar
,0.5秒后变为baz
此特点对比CommonJS
,CommonJS
中的模块输出的是值的缓存
注意事项
export
可以出现在模块的任意位置,但需要在模块顶层- 如果处于
块级作用域
会报错,因为处于条件代码块中会阻碍静态优化,违背了es6模块设计的初衷
八、关于import
命令
import
命令接受一对大括号,里面指定要从其他模块导入的变量名
——见例1- 大括号里的变量名必须与被导入模块对外接口的
名称相同
- 如果想为引入的变量重命名,可以在引入的时候使用
as
为其重命名——见例2 import
命令输入的变量都是只读
的,但变量的属性可以被改写,但这种操作很危险,会影响到其他引入该模块的地方——见例3(类似于const声明的对象,可以改变其内部的属性,模块export
输出的接口相当于一个对象)
例1
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
例2
import { lastName as surname } from './profile.js';
例3
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
引入方式
- 可以是相对路径,也可以是绝对路径
.js
后缀可以省略- 直接引入模块名不带路径,需要有
配置文件
告诉js引擎该模块的位置
注意事项
import
具有提升效果,会提升到整个模块的头部首先执行(本质因为import
命令是编译阶段执行,在代码运行之前)——见例1- 由于
import
静态执行,所以不能用表达式
和变量
,因为这些都是在运行时
才能得到结果的语法接口——见例2
例1
foo();
// 虽然位置在下面,但执行早于 foo() 调用
import { foo } from 'my_module';
例2
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
注意事项
import
语句会执行所加载的模块- 多次重复执行同一句
import
语句只会执行一次(单例模式)——见例1 - 目前,通过
babel
转码,CommonJS
模块的require
命令和es6
模块的import
命令可以共存,但不建议同时使用,因为import
在静态解析
阶段执行,在一个模块之中最早执行,可能会使得require
语句不按预期执行——见例2
例1
import 'lodash';
import 'lodash';
// 只会加载一次
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
// 只会加载一次
例2
require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
// 最先执行
import React from 'React';
九、模块整体加载
方式
用*
号指定一个对象,所有模块的输出值都加载在这个对象上
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
逐一加载
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
注意点
模块整体加载所在的那个对象是可以静态分析的,不允许运行时改变
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};