Python: WEB开发-JS
- TAGS: Python
JS1
基础语法
文档
搜索MDN,Mozilla Developer Network,提供非常完善HTML、CSS、JS等的技术资料。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
指南 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide 非常好的JS文档。
使用任何一种文本编辑器,都可以开发JS,此次使用微软的Visual Studio Code进行开发。
注释
和C、Java一样
- // #单行注释
- * 注释 * #多行注释,也可以用在语句中
范例
/** * aaa * bbb */ str = 'hello' + /* comment */ 'abc'; console.log(str); console.log('hello' + /* comment */ 'abc'); // 单行注释
常量和变量
标识符
标识符必须是字母、下划线、美元符号$和数字,但必须是字母、下划线、美元符号开头,依然是不能数字开头就行 标识符区分大小写
声明
- var 声明一个变量。不能突破函数作用域
- let 推荐,声明一个块作用域中的局部变量。小范围使用,不能突破大括号作用域,更加不能突破函数作用域
- const 声明一个常量。常量不允许改变, 常量不能分开写,必须立即赋值
JS中的变量声明和初始化是可以分开的
var a, d // 只是声明,a d为undefined let b console.log(1,a,b) a = 1 b = 'a string' console.log(2,a,b) //const c // 不可以 const c = 100 // 常量必须声明时赋值,之后不能再改 console.log(c) //c = 200 // 不可以更改
var y //只是声明,y值为undefined var x = 5 // 规范的声明并初始化,声明全局或局部变量。 z = 6 //不规范的初始化,不推荐。在严格模式下会产生异常。在赋值之前不能引用,因为它没有声明。一旦这样赋值就是全局作用域
范例
console.log(x, y); //x, y声明提升了。但没什么意义,仅作来判断 a = 5; // 隐式声明,不会有提升 console.log(a); var x, y; // 只是声明,动态弱类型语言,声明并不代表给定类型。声明提升 console.log(x, y); a = 'abc'; console.log(a); // ES javascript 超集 Typescript 中支持了类型 let z = 789; // 不会声明提升 console.log(z); z = 123; console.log(z); // const c; // 不可以 const c = 100 // 常量必须声明时赋值,之后不能再改。 // js中推荐如果此标识符不再改变指向的内容,推荐使用常量 console.log(c) //c = 200 // 不可以更改
返回结果
undefined undefined 5 undefined undefined abc 789 123
函数
function a() { x = 100; // global变量,不要用,不好,严格模式直接报错 var y; // 局部变量,作用域在函数中 y = 200; // 局部变量 let z = 300; console.log(x, y, z); } a(); console.log(x); // console.log(z) //未声明变量z,报异常 //y = 200 // 不能声明提升 //let y = 200 // 不能声明提升 // var y = 300; a(); // var声明提升hoisting
var会把变量提升到全局或当前函数作用域。
常量的选择
- 如果明确知道一个标识符定义后不再修改,应该尽量声明成const常量,减少被修改的风险,减少Bug
数据类型
名称 | 说明 |
---|---|
number | 数值型,包括整型和浮点型 |
boolean | 布尔型,true和false |
string | 字符串。需用单双引号、反引号 |
null | 只有一个值null |
undefined | 变量声明未赋值的;对象未定义的属性 |
symbol | ES6 新引入类型 |
object类型 | 是以上基本类型的复合类型,是容器 |
ES是动态语言,弱类型语言。
虽然先声明了变量,但变量可以重新赋值为任何类型。
动态语言: 变量使用时无需事先声明类型 弱类型语言: 字符串加整型,不报错就是弱类型 console.log('a' + 1) // a1 虽然先声明了变量,但是变量可以重新赋值任何类型
对象的定义
let a = {a:100} console.log(a.a) // 100 b = 200 let c = {b} console.log(c.b) // 200 d = 1000 e = 2000 let f = {d, e} console.log(f) // { d: 1000, e: 2000 }
类型转换
// 类型转换 // 弱类型 // 字符串 console.log('====string====') // 都被隐式转换为string类型然后拼接 console.log(a = 3 + 8, typeof(a)) // 3hello string console.log(a = null + 'hello', typeof(a)) // nullhello string console.log(a = undefined + 'hello', typeof(a)) // undefinedhello string console.log(a = true + 'hello', typeof(a)) // truehello string // 数字 console.log('====number====') // 都隐式转换为数值然后做数值加法 console.log(a = null + 8, typeof(a)) // 8 'number' console.log(a = undefined + 8, typeof(a)) // NaN 'number' //undefined没法转换成一个对应的数字 Not a Number console.log(a = true + 8, typeof(a)) // 9 'number' console.log(a = false + 8, typeof(a)) // 8 'number' // 布尔 console.log('====bool====') // 都隐式转换为数值 console.log(a = null + true, typeof(a)) // 1 'number' console.log(a = null + false, typeof(a)) // 0 'number' console.log(a = undefined + true, typeof(a)) // NaN 'number' //undefined没法转换成一个对应的数字 console.log(a = undefined + false, typeof(a)) // NaN 'number' console.log(a = null & true, typeof(a)) // 0 'number' //位与&; 位或| console.log(a = undefined & true, typeof(a)) // 0 'number' // 短路 and && or || not ! console.log(a = null && true, typeof(a)) // null 'object' //逻辑运算符,null 直接就是false短路 console.log(a = false && null, typeof(a)) // false 'boolean' console.log(a = false && 'hello', typeof(a)) // false 'boolean' console.log(a = true && 'hello', typeof(a)) // hello string console.log(a = true && '' && 'abc', typeof(a)) // string // 第一个:逻辑运算符,null 直接就是false短路 // 第二个:逻辑运算符,false短路返回false // 第三个:boolean // 第三个:字符串 // 第五个:返回的是空字符串,看不见。 // null console.log('====null====') console.log(a = null + undefined, typeof(a)) // NaN 'number' // 短路补充 {} [] 都不能像Python一样把它当作False来看,它们都是对象,当真看 console.log(a = [] && 'abc', typeof(a)) // abc string console.log(a = {} && 'abc', typeof(a)) // abc string
弱类型,不需要强制类型转换,会隐式类型转换。
NaN,即Not a Number,转换数字失败。它和任何值都不等,和自己也不等,只能使用Number.isNaN(NaN)判断。
总结:
- 遇到字符串,加号就是拼接字符串,所有非字符串隐式转换为字符串。
- 如果没有字符串,加号把其他所有类型都当数字处理,非数字类型隐式转换为数字。undefined特殊,因为它都没有定义值,所以转换数字失败得到一个特殊值NaN。
- 如果运算符是逻辑运算符,短路符,返回就是短路时的类型。没有隐式转换。
- 除非你十分明确,否则不要依赖隐式转换。写代码的时候,往往为了程序的健壮,请显式转换。
注意: 以上的原则不要死记,忘了就实验,或者显式的类型转换
字符串
将一个值使用’ 单引号或者 "双引号 引用起来就是字符串。
ES6提供了反引号 定义一个字符串,可以支持多行,还支持插值
let a = "abc"; let b = "135\t\r\n"; let c = `line1 line2 line3 `; // 支持多行 console.log(c); // 字符串插值,要求在反引号字符串中.python3.6支持 let name = "tom", age = 19; console.log(`my name is ${name}. I am ${age * 2}`);
返回结果
line1 line2 line3 my name is tom. I am 38
转义字符
名称 | 说明 |
---|---|
\0 | ASCII 0,Null字符,空字符 |
\b | 退格符 |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | Tab(制表符) |
\v | 垂直制表符 |
’ | 单引号 |
" | 双引号 |
\ | 反斜杠字符 ( \ ) |
\XXX | 由从0到377最多三位八进制数XXX表示的Latin-1字符。例如,\251是版本符号的八进制序列 |
\xXX | 由从00到FF的两位十六进制数字XX表示的Latin-1字符。例如,\xA49是版本符号的十六进制序列 |
\uXXXX | 由四位十六进制数字XXXX表示的Unicode字符。例如,\u00A9是版本符号的Unicode序列。见Unicode escape sequences(Unicode 转义字符) |
\u{XXXXX} | Unicode代码点(code point)转义字符。例如,\u{2f804}相当于Uicode转义字符, \uD87E\uDC04的简写 |
字符串操作方法
字符串操作方法很多,但和Python类似
let test = "Python"; console.log(test.charAt(2)); // t 索引 console.log(test[2]); // t 索引 常用 console.log(test.toUpperCase()); // PYTHON console.log(test.concat(".org")); // Python.org 字符串拼接 console.log(test.concat('-', "org") + '.cn'); // Python-org.cn 字符串拼接 console.log(test.slice(3)); // hon 切片,支持负索引 console.log(test.slice(3, 5)); // ho [3, 5)前包后不包 console.log(test.slice(-2, -1)); // o console.log(test.slice(-2)); // on console.log(test.repeat(3)); // PythonPythonPython 字符串重复 console.log(test.endsWith("n")); // true console.log(test.endsWith("P")); // false console.log(test.search("t")); // 2 console.log(test.length); // 6 长度 let url = "www.python.org"; console.log(url.split(".")); // [ 'www', 'python', 'org' ] console.log(url.split(".", 2)); // [ 'www', 'python' ] 分割几次 console.log('a+b+c'.split(/\w/)) // [ '', '+', '+', '' ] 正则表达式分割 console.log(url.substr(7, 2)); // ho 返回字符串从何处开始,取多长 将废弃 console.log(url.substring(7, 10)); //hon 返回子串,从何处开始,到什么为止,前包后不包 let s = "python.hon"; console.log(s.indexOf("ho")); // 3 console.log(s.indexOf("ho", 4)); // 7 console.log(s.replace(".hon", ".com")); // python.com 字符串替换 s = "\tpyt hon \r\n"; console.log(s.trim()); // pyt hon 去除两端的空白字符 // trimLeft、trimRight是非标函数,少用
数值型number
在JS中,数据均为双精度浮点型范围只能在-(2^53 -1) 和 2^53 -1之间,整型也不例外。
数字类型还有三种符号值
- +Infinity(正无穷)
- -Infinity(负无穷)
- NaN (not-a-number非数字)
- 二进制0b0010、0B110。
- 八进制0755。注意0855,将被认作十进制,因为8不在八进制中。ES6中最好使用0o前缀表示八进制。
- 十六进制0xAA、0Xff。
- 指数表示1E3(1000),2e-2(0.02)
范例
let a = 1 + undefined; console.log(a); // NaN console.log(a == NaN) // false console.log(NaN == NaN) // false console.log(Number.isNaN(a)) // true let b = 1 / 2; console.log(b, typeof b); // 0.5 number let c = 1 / 0; console.log(c, typeof c); // Infinity number 正无穷 let d = -1 / 0; console.log(c, typeof d); // -Infinity number 负无穷
常用属性
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308 console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 console.log(Number.MIN_VALUE); // 5e-324 console.log(Number.POSITIVE_INFINITY); // Infinity 正无穷 console.log(Number.NEGATIVE_INFINITY); // -Infinity 负无穷 console.log(Number.NaN); // NaN 非数字
数字的方法
方法 | 描述 |
---|---|
Number.parseFloat() | 把字符串参数解析成浮点数,和全局方法 parseFloat() 作用一致 |
Number.parseInt() | 把字符串解析成特定基数对应的整型数字,和全局方法 parseInt() 作用一致 |
Number.isFinite() | 判断传递的值是否为有限数字 |
Number.isInteger() | 判断传递的值是否为整数 |
Number.isNaN() | 判断传递的值是否为 NaN |
范例:
let a = 0x712; console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 console.log(Number.isNaN(NaN)); // true console.log(Number.NaN == Number.NaN); // false NaN不能用等值来做 只能用判断来做
内置数学对象Math
- Math提供了绝对值、对数指数运算、三角函数运算、最大值、最小值、随机数、开方等运算函数,提供了PI值
console.log(Math.PI, Math.E, Math.log2(16), Math.sqrt(2)); // 3.141592653589793 2.718281828459045 4 1.4142135623730951 console.log(Math.pow(2, 3), 2 ** 3, 2 ** 0.5); // 8 8 1.4142135623730951 console.log(Math.abs(-1)); // 1 console.log(Math.random()); // (0,1)之间
Symbol类型
ES6提供Symbol类型,内建原生类型。
let s = Symbol(); console.log(s); // Symbol() let s3 = Symbol(); console.log(s3); // Symbol() let s1 = Symbol('key1'); let s2 = Symbol('key2'); console.log(s1, s2); // Symbol(key1) Symbol(key2) console.log(s1 === s2); // false Symbol值是唯一的 console.log(typeof s1); // symbol
1、作为对象的属性key
let s = Symbol(); let t = 'abc'; let a = { [s]:'xyz', // symbol做key, 注意要使用中括号,这个key一定唯一 t:'ttt', [t]:'ooo', // 中括号内可计算 1:'111', // 数字做key, 会被自动转换为字符串 } console.log(a); // { [Symbol()]: 'xyz', t: 'ttt', abc: 'ooo' } console.log(a[s]); // xyz a[s] = 2000; console.log(a[s]); // 2000 console.log(a.abc, a.t, a[1], a['abc'], a[Symbol()]); // ooo ttt 111 ooo undefined
2、构建常量
// 以前用法。其实有变量名就可以知道代表什么意思 var COLOR_RED = 'RED'; var COLOR_BLUE = 'BLUE'; var COLOR_BLACK = 'BLACK'; var COLOR_GREEN = 'GREEN'; // 现在用法 const COLOR_RED = Symbol(); const COLOR_BLUE = Symbol(); const COLOR_BLACK = Symbol(); const COLOR_GREEN = Symbol();
应用
let color = { RED: 1, BLUE: 2, BLACK: 3, GREEN: 4, }; RED = 1; BLUE = 2; console.log(color.BLACK); // 3 let newColor = { ...color, RED: Symbol(), BLACK: Symbol(), }; console.log(newColor); // { RED: Symbol(), BLUE: 2, BLACK: Symbol(), GREEN: 4 }
运算符
算数运算符
- + - * / %等运算符和Python一样
- parseInt 直接截取 整数部分
- Math.ceil 向上取整
- Math.floor 向下取整
- Math.round
- 6入,取绝对值更大的值。1.6取2,-1.6取-2
- 4舍,取绝对值更小的值。1.4取1,-1.4取-1
- 5,取数轴向右离自己最近的整数。
console.log(1 / 2); console.log(1 / 0); console.log(5 % 3); // 整除 pareseInt Math.floor Math.ceil Math.round console.log(parseInt(1 / 2), parseInt(3 / 2), parseInt(5 / 2)); // 0 1 2 console.log(parseInt(-1 / 2), parseInt(-3 / 2), parseInt(-5 / 2)); // -0 -1 -2 console.log('-'.repeat(30)); console.log(Math.floor(1 / 2), Math.floor(3 / 2), Math.floor(5 / 2)); // 0 1 2 console.log(Math.floor(-1 / 2), Math.floor(-3 / 2), Math.floor(-5 / 2)); // -1 -2 -3 console.log('-'.repeat(30)); console.log(Math.ceil(1 / 2), Math.ceil(3 / 2), Math.ceil(5 / 2)); // 1 2 3 console.log(Math.ceil(-1 / 2), Math.ceil(-3 / 2), Math.ceil(-5 / 2)); // -0 -1 -2 console.log('-'.repeat(30)); // round 正数四舍五入; 负数四舍六入,五向上 console.log(Math.round(1 / 2), Math.round(3 / 2), Math.round(5 / 2)); // 1 2 3 console.log(Math.round(-1 / 2), Math.round(-3 / 2), Math.round(-5 / 2)); // -0 -1 -2
++ 和 –
- 单目运算符,代表变量自增、自减
- i++ 先用i,用完之后i再自增加1
- ++i i先自增,再使用i
let a = 0; console.log(a++); // 0 a++ 先用后加 console.log(a++); // 1 console.log(++a); // 3 ++a 先加后用 console.log(a); // 3 i = 0; a = ++i + i++ + i++ + i; // 等价于 (++i) + (i++) + (i++) + i console.log(a); // / 1 + 1 + 2 + 3 =7
1、单目运算符优先级高于双目运算符
2、加号+是双目运算符,两边的表达式必须先计算好
比较运算符
> < >= <= 没有什么区别 != == !== === == 宽松相等,进行类型转换,隐式类型转换 === 严格相等,不进行类型转换,内容相等,类型相等 安全代码,建议使用 三等号
console.log(100 > 200); // false console.log(100 > "200"); // false console.log(300 > "200"); // true console.log(1000 > "200"); // true console.log("1000" > 200); // true console.log("100" > "200"); // false console.log("1000" > "200"); // false console.log("-".repeat(30)); // 类型转换失败 为false console.log(1000 > "200a"); // false console.log(1000 > "0a"); // false console.log(1000 > "a"); // false console.log(1000 > NaN); // false console.log(1000 < NaN); // false //宽松比较 console.log(100 == 200); // false console.log(100 == "200"); // false console.log(200 == "200"); // true / /严格比较 === console.log(200 === "200"); // false
逻辑运算符
&& 、|| 、 !与、或、非 这些运算符和其他高级语言都一样,支持短路
位运算
& | ^ ~ << >> 位与、位或、异或、取反、左移、右移,和Python一样 异或,相异出1
三元运算符
条件表达式?真值:假值 等价于简单的if...else结构 if (条件表达式) { 真值 } else { 假值 }
// python中 真值 if test else 假值 c/javascript中 test?真值:假值 // ?: 是三元运算符,三目 let a = 50; console.log(a > 100?'>100':'<=100'); console.log(a > 100?'>100':(a > 50?'>50':'<=50'));
逗号操作符
JS运行多个表达式写在一起
return 会返回最后一个变量的值
let v; v = 6, true; // python中,逗号优化级高于=。类c中,逗号优化级低于= console.log(v); // 6 let a = 4 + 5, b = true, c = a > 20 ? "t" : "f"; console.log(a, b, c); // 9 true 'f' function test() { return 3, a + b, c = a++; // 逗号表达式,返回最后一个表达式的值 } console.log(test()); // 9 console.log(c); // 9 console.log(a); // 10
其他
名称 | 说明 |
---|---|
instanceof | 判断是否属于指定类型 |
typeof | 返回类型字符串 |
delete | delete操作符, 删除一个对象(an object)或一个对象的属性(an object’s property)或者一个数组中某一个键值(an element at a specified index in an array) |
in | 如果指定的属性在对象内,则返回true |
console.log('a' instanceof String); // false console.log(1 instanceof Number); // false //构造实例 let a = new String('a'); console.log(a, a instanceof String,) ; // [String: 'a'] true console.log(b = new Number(1), b instanceof Number) ; // [Number: 1] true console.log([] instanceof Array) // true console.log([] instanceof Object) // true console.log({} instanceof Array) // false console.log({} instanceof Object) // true console.log(typeof 'a', typeof 'a' == 'string'); // string true console.log(typeof 1, typeof 1 == 'number'); // number true console.log(typeof a, typeof a == 'string'); // object false console.log(typeof b, typeof b == 'number'); // object false console.log(typeof a, typeof a === 'object'); // object true
instanceof 要求必须明确使用类型定义变量,就是对象必须是new关键字声明创建的。它可以用于继承关系的判断。
typeof就是返回对象的类型字符串。
delete 删除对象、属性、数组元素
- 删了数组的元素,个数不会少,会补充undefined
x = 42; //隐式声明 var y = 43; let z = 60; myobj = new Number(); myobj.h = 4; // create property h console.log(delete x); // returns true (can delete if declared implicitly) console.log(delete y); // returns false (cannot delete if declared with var) console.log(delete z); // returns false console.log(delete Math.PI); // returns false (cannot delete predefined properties) console.log(delete myobj.z); // returns true (can delete user-defined properties) console.log(delete myobj); // returns true (can delete if declared implicitly) console.log("~~~~~~~~~~~~~~~~~~~~"); var trees = new Array("redwood", "bay", "cedar", "oak", "maple"); for (let i = 0; i < trees.length; i++) console.log(trees[i]); // for循环中只有一条语句,{}可以省略 console.log("=================="); delete trees[3]; // 数组中元素被删除,但空着的位置是undefined for (var i = 0; i < trees.length; i++) console.log(trees[i]);
返回结果
true false false false true true ~~~~~~~~~~~~~~~~~~~~ redwood bay cedar oak maple ================== redwood bay cedar undefined maple
in 判断属性是否在对象内
// Custom objects let mycar = { color: "red", year: 1998, }; console.log("color" in mycar); // returns true console.log("model" in mycar); // returns false console.log("year" in mycar); // true
console.log('~~~~~~~~~~~~~~~~~~~~') let trees = new Array("redwood", "bay", "cedar", "oak", "maple"); console.log(0 in trees); // returns true ,0在数组对象的index中 console.log(3 in trees); // returns true ,3在数组对象的index中 console.log(6 in trees); // returns false,6不在数组对象的index中 console.log("bay" in trees); // return false,bay不是属性,它是值 console.log("length" in trees); // returns true,length是对象的属性 console.log('~~~~~~~~~~~~~~~~~~~~') delete trees[3]; console.log(3 in trees); // return false for(var i=0;i<trees.length;i++) console.log(trees[i]); //in 判断的是属性在不在,数组来说索引和length都是属性
返回结果
~~~~~~~~~~~~~~~~~~~~ true true false false true ~~~~~~~~~~~~~~~~~~~~ false redwood bay cedar undefined maple
pop
- 可以pop掉,删除最后一个元素
- 长度会变短,元素会移除
var trees = new Array("redwood", "bay", "cedar", "oak"); for (var i = 0; i < trees.length; i++) console.log(trees[i]); console.log("============="); trees.pop(); for (var i = 0; i < trees.length; i++) console.log(trees[i]);
执行结果
redwood bay cedar oak ============= redwood bay cedar
运算符优先级
运算符由高到低,顺序如下
. [] () new ! ~ - + ++ -- typeof void delete * / % + - << >> >>> < <= > >= in instanceof == != === !== & 位与 ^ 异或 | 位或 && || ?: 三目运算符 = += -= *= /= %= <<= >>= >>>= &= ^= |= ,
单目 > 双目 > 三目 > 赋值 > 逗号
逗号运算符优先级最低,比赋值语句还低。
记不住,就使用括号。
表达式
基本表达式,和Python差不多。
解析式也和Python的相似,但在ES6中非标准不推荐使用。
生成器推荐使用生成器函数,ES6开始支持。
function* inc() { let x = 0, y = 7; while (true) { yield x++; if (!y--) return 100; } } let gen = inc(); // 生成器函数调用后,返回一个惰性的生成器对象 console.log(gen); for (let i = 0; i < 10; i++) { console.log(gen.next()); //使用该对象的next方法,返回一个对象,包含value和done属性 }
执行结果
{ value: 0, done: false } { value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: 4, done: false } { value: 5, done: false } { value: 6, done: false } { value: 7, done: false } { value: 100, done: true } { value: undefined, done: true }
每次调用next()方法返回一个对象,这个对象包含2个属性:value和done,value属性表示本次 yield表达式的返回值,done属性为布尔类型。done是false表示后续还有yield语句执行,如果 执行完成或者return后,done为true.
JS语法
语句块
JS使用大括号构成语句块。 ES6 之前语句块是没有作用域的,从ES6开始支持 块作用域 ,let只能在块作用域内可见
function hello(){ let a = 1; var b = 2; c = 3 } if (1){ let d = 4; //不能突破函数、{}块作用域名 var e = 5; //当前作用域,不能突破函数 f = 6 //全局作用域 if (true){ console.log(d) // 可见 console.log(e) // 可见 console.log(f) // 可见 console.log('----------') g = 10 var h = 11 } } // console.log(a) 不可见 // console.log(b) 不可见 // console.log(c) 不可见,函数为执行 hello() console.log(c) // 块作用域隐式声明,可见 // console.log(d) // 块作用域使用let,不可见;但是块外的d可见 console.log(e)) // 块作用域使用var,可见 console.log(f)) // 块作用域隐式声明,可见 console.log(g)) // 可见 console.log(h)) // 可见
let 不能突破函数、{ } 块作用域 var 不能突破函数作用域 c = 3 隐式申明,为全局作用域
流程控制
条件分支
if (cond1){ } else if (cond2) { } else if (cond3) { } else { }
条件的False等效 false undefined null 0 NaN 空字符串 其它值都将被视为True
switch…case分支语句
switch (expression) { case label_1: statements_1 [break;] case label_2: statements_2 [break;] ... default: statements_def [break;] }
当进入case语句后,如果没有break语句,会产生穿透现象;一直穿透到break语句结束; 如果一直没有,将把代码执行完.
穿透问题,一定要在case中恰当的使用break语句,否则就会继续顺序向下执行
let x = 5; // 换成1试一试 switch (x) { case 0: console.log("zero"); break; case 1: console.log("one"); case 2: console.log("two"); case 3: console.log("three"); break; case 5: case 4: console.log("four"); default: console.log("other"); // break; }
执行结果
four other #如果x = 2,执行结果为 two three
switch…case语句都可以写成多分支结构。
for循环
// C风格for循环 for ([initialExpression]; [condition]; [incrementExpression]) //setup执行一次; 每次比较, true才会进行循环; 每次做完循环体后要做的事post { statement }
for (let i = 0; i < 5; i++) { console.log(i); } console.log("-".repeat(30)); for (var x = 0, y = 9; x < 5; x++, y--) { console.log(`${x * y}`); } console.log("-".repeat(30)); for (let i = 0; i < 10; i += 3) { // 步长 console.log(i); } // for (;;); // for循环版死循环 等价for(;;){}
while循环 和 do…while循环
条件满足,进入循环,条件为真,继续循环
while (condition)
statement
先进入do语句循环,然后判断,为真就继续循环
do statement while (condition);
// while (1){ //死循环 // ; // } // while (true); // do {;} while(true) //死循环
let x = 5
while (x–){
console.log(x);
}
console.log('~~~~~~~~
')
do {
console.log(x)
}
while(x++<5)
范例
let x = 5; while (x--) { console.log(x); } console.log("~~~~~~~~~~"); do { console.log(x); // 从-1开始 } while (x++ < 5);
执行结果
4 3 2 1 0 ~~~~~~~~~~ -1 0 1 2 3 4 5
练习-九九乘法表
for (let i = 1; i < 10; i++) { line = ""; for (let j = 1; j <= i; j++) { line += j + "*" + i + "=" + j * i + " "; // line += `${j}*${i}=${i*j?'t':'f'} ` } console.log(line); }
for…in循环
对象操作语句for…in用来遍历对象的属性
for (variable in object) { statements }
// 数组 let arr = [10, 20, 30, 40]; console.log(arr[1]); // 20 console.log(10 in arr); // false in属性 console.log(0 in arr); // true arr[0] console.log("length" in arr); // true console.log("-".repeat(30)); for (let x in arr) { // 遍历对象属性 console.log(x); // 返回索引(属性) } console.log("-".repeat(30)); for (let index in arr) { console.log(`${index} : ${arr[index]}`); //插值 } console.log("-".repeat(30)); // C风格 for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } console.log("-".repeat(30)); // 对象 let obj = { a: 1, b: "hello", c: true, }; console.log(obj.a); console.log(obj["b"]); // 对象属性当索引访问 console.log(obj.d); // undefined console.log("-".repeat(30)); for (let x in obj) { console.log(x); // 属性名 } console.log("-".repeat(30)); for (let key in obj) { // 返回数组的index console.log(`${key} : ${obj[key]}`); }
执行结果
20 false true true ------------------------------ 0 1 2 3 ------------------------------ 0 : 10 1 : 20 2 : 30 3 : 40 ------------------------------ 10 20 30 40 ------------------------------ 1 hello undefined ------------------------------ a b c ------------------------------ a : 1 b : hello c : true
for in 循环返回的是索引或者key,需要间接访问到值。
数组反正返回的是索引,C风格for循环操作可能方便点。根据个人喜好选择。
对象用for in合适。
for…of 循环
ES6的新语法
let arr = [1, 2, 3, 4, 5]; let obj = { a: 1, b: "hello", c: true, }; for (let i of arr) { //返回数组元素 console.log(i); } // for (let i of obj) { // 异常,不可以迭代 // console.log(i); // }
注意 for … of 不能迭代一个普通对象
原因是,of后面必须是一个迭代器(TypeError: obj[Symbol.iterator] is not a function)
可类比python中的for in,例如for x in [ ]
break 、 continue
break 结束当前循环
continue 中断当前循环,直接进入下一次循环
for迭代的差别
function sum(arr) { for (let x in arr) {// 遍历index或对象属性 console.log(x, typeof x, arr[x]); } console.log("------------------"); for (let x of arr) {// 遍历元素 console.log(x, typeof x); } console.log("------------------"); for (let x = 0; x < arr.length; x++) {// 自己定义索引数值遍历 console.log(x, typeof x, arr[x]); } } sum([3, 6, 9]);
执行结果
0 string 3 1 string 6 2 string 9 ------------------ 3 number 6 number 9 number ------------------ 0 number 3 1 number 6 2 number 9
Symbols类型
ES6提供Symbol类型,内建原生类型 symbol值是唯一的
let sym1 Symbol()
let sym2 = Symbol('key1')
let sym3 = Symbol('key2')
console.log(sym2 =
sym3) //false
作为对象的属性key
let s = Symbol() let t = 'abc' let a = { [s]:'xyz', // symbol做key,注意要使用中括号,这个key一定唯一 t:'tt', [t]:'oo' }
console.log(a) console.log(a[s]) a[s] = 2000 console.log(a[s])
// 执行结果 { t: 'tt', abc: 'oo', [Symbol()]: 'xyz' } xyz 2000
构建常量
以前的用法 var COLOR_RED = 'RED'; var COLOR_ORANGE = 'ORANGE'; var COLOR_YELLOW = 'YELLOW'; var COLOR_GREEN = 'GREEN'; var COLOR_BLUE = 'BLUE'; var COLOR_VIOLET = 'VIOLET';
现在的用法 const COLOR_RED = Symbol(); const COLOR_ORANGE = Symbol(); const COLOR_YELLOW = Symbol(); const COLOR_GREEN = Symbol(); const COLOR_BLUE = Symbol(); const COLOR_VIOLET = Symbol();
JS语法-函数与异常
函数
function 函数名(参数列表) { 函数体; return 返回值; } function add(x,y){ return x+y; } console.log(add(3,5));
函数表达式
使用表达式来定义函数,表达式中的函数名可以省略,如果这个函数名不省略,也只能用在此函数内部
匿名函数
const add = function(x, y){ return x + y; }; console.log(add(4, 6)
有名字的函数表达式
const sub = function fn(x, y) { return x -y; } console.log(sub(5, 3)) // console.log(fn(3, 2)) // fn 只能用在函数内部
内部使用,相当于递归调用
const sub = function fn(n) { if (n===1) return n; return n + fn(--n) // fn 只能在函数内部使用 // return n===1?n:n+fn(--n) } console.log(sub(4))
函数、匿名函数、函数表达式的差异
函数和匿名函数,本质上都是一样的,都是函数对象,只不过函数有自己的标识符——函数名,匿名函数需要借助其它的标识符而已。
区别在于,函数会声明提升,函数表达式不会。
console.log(add(4,6)) // 匿名函数 function add(x, y){ // 声明提升 return x + y; }; // console.log(sub(5, 3)); // sub 未定义 // 有名字的函数表达式 const sub = function (x,y){ return x - y; }; console.log(sub(5, 3));
养成基本习惯,函数在声明定义后使用,不要用声明提升。
高阶函数
高阶函数:函数作为参数或返回一个函数
完成一个计数器counter
const counter = function(){ let c = 0 return function(){ return ++c; }; }; const c = counter() console.log(c()) // 1 console.log(c()) // 2 console.log(c()) // 3
另附counter的生成器版本,仅供参考
const counter = (function * () { let c = 1 while (true) { yield c++ } })() console.log(counter.next()) console.log(counter.next()) console.log(counter.next())
执行结果
{ value: 1, done: false } { value: 2, done: false } { value: 3, done: false }
练习-完成一个map函数
完成一个map函数:可以对某一个数组的元素进行某种处理
let map = function (func, iterable) { let newArr = []; for (let v of iterable) { newArr.push(func(v)) } return newArr; } console.log(map(function(x) { return x + 1}, [1, 2, 3]));
方法2
let map = function (func, iterable) { let newArr = []; for (let index in iterable) { newArr[index] = func(iterable[index]); } return newArr; } console.log(map(function(x) { return x + 1}, [1, 2, 3])); //[ 2, 3, 4 ] console.log(map(function(x) { return x++}, [1, 2, 3])); //[ 1, 2, 3 ] console.log(map(function(x) { return ++x}, [1, 2, 3])); //[ 2, 3, 4 ] console.log(map(function(x) { return x += 1}, [1, 2, 3])); //[ 2, 3, 4 ]
箭头函数
箭头函数就是匿名函数,它是一种更加精简的格式。
将上例中的函数更改为箭头函数
const map = function(arr,fn){ let newarr = []; for (let i in arr){ newarr[i] = fn(arr[i]); } return newarr } // 以下三行等价 console.log(map([1,2,3,4], (x) => {return x*2})); console.log(map([1,2,3,4], x => {return x*2})); console.log(map([1,2,3,4], x => x*2));
箭头函数参数
- 如果一个函数没有参数,使用()
- 如果只有一个参数,参数列表可以省略小括号()
- 多个参数不能省略小括号,且使用逗号间隔
箭头函数返回值
- 如果函数体部分有多行,就需要使用{},如果有返回值使用return
- 如果只有一行语句,可以同时省略大括号和return
只要有return语句,就不能省略大括号
console.log(map([1,2,3,4], x => {return ++x})) ,有return必须有大括号
如果只有一条非return语句,加上大括号,函数就成了无返回值
console.log(map([1,2,3,4], x => {x*2})); 加上了大括号,它不等价于x =>{return x*2}
因此,记住 x => x*2 这种正确的形式就行
函数参数
普通参数
一个参数占一个位置,支持默认参数
const add = (x, y) => x + y console.log(add(4, 5)) // 9 // 缺省值 const add1 = (x, y=5) => x + y console.log(add1(4, 7)) // 11 console.log(add1(4)) // 9
测试下面代码
const add = (x=6, y) => x + y console.log(add()) // NaN console.log(add(1)) // NaN console.log(add(y=2, z=3)) // 5 console.log(add(3, 4, 5, 6)) // 7
观察上面diamagnetic执行结果,原因
- JS中并没有Python中的关键字传参
- JS只是做参数位置的对应
- JS并不限制默认参数的位置
- JS传参个数超范围,多余的参数不影响函数调用
add2(y=2,z=3)相当于add2(2,3),因为JS没有关键字传参,但是它的赋值表达式有值值,y=2就是2,z=3就是3
建议,默认参数写到后面,这是一个好的习惯
可变参数(rest parameters剩余参数)
JS使用…表示可变参数(Python用*收集多个参数)
const sum = function (...args){ let result = 0; console.log(...args) // 3 6 9 参数解构 for (let x in args){ result += args[x] }; return result }; console.log(sum(3, 6, 9)) // 18
arguments对象
函数的所有参数会被保存在一个arguments的键值对对象中
(function (p1, …args) { console.log(p1) console.log(args) console.log('-----------–—') console.log(arguments) / 对象 for (let x of arguments) / 该对象可以使用of console.log(x); })('abc', 1,3,5)
(function (p1, ...args) { console.log(p1); console.log(args); console.log("----------------"); console.log(arguments); // 对象 for (let x of arguments) // 该对象可以使用of console.log(x); })("abc", 1, 3, 5);
执行结果
abc [ 1, 3, 5 ] ---------------- [Arguments] { '0': 'abc', '1': 1, '2': 3, '3': 5 } abc 1 3 5
ES6之前,arguments是唯一可变参数的实现。
ES6开始,不推荐,建议使用可变参数。为了兼容而保留。
注意,使用箭头函数,取到的arguments不是我们想要的,如下
((x, ...args) => {
console.log(args);
console.log(x);
console.log(arguments);
})(...[1,2,3,4])
[ 2, 3, 4 ] 1 [Arguments] { '0': {}, '1': [Function: require] { resolve: [Function: resolve] { paths: [Function: paths] }, main: { id: '.', path: 'D:\\project\\pyproj\\trae', exports: {}, filename: 'D:\\project\\pyproj\\trae\\index.js', loaded: false, children: [], paths: [Array], [Symbol(kIsMainSymbol)]: true, [Symbol(kIsCachedByESMLoader)]: false, [Symbol(kIsExecuting)]: true }, extensions: [Object: null prototype] { '.js': [Function (anonymous)], '.json': [Function (anonymous)], '.node': [Function (anonymous)] }, cache: [Object: null prototype] { 'D:\\project\\pyproj\\trae\\index.js': [Object] } }, '2': { id: '.', path: 'D:\\project\\pyproj\\trae', exports: {}, filename: 'D:\\project\\pyproj\\trae\\index.js', loaded: false, children: [], paths: [ 'D:\\project\\pyproj\\trae\\node_modules', 'D:\\project\\pyproj\\node_modules', 'D:\\project\\node_modules', 'D:\\node_modules' ], [Symbol(kIsMainSymbol)]: true, [Symbol(kIsCachedByESMLoader)]: false, [Symbol(kIsExecuting)]: true }, '3': 'D:\\project\\pyproj\\trae\\index.js', '4': 'D:\\project\\pyproj\\trae' }
参数解构
和Python类似,Js提供了参数解构,依然使用了…符号来解构
const add = (x, y) => {console.log(x, y);return x + y;}; console.log(add(...[100, 200])); // 300 console.log(add(...[100, 200, 300, 3, 5, 3])); // 300 console.log(add(...[100])); // NaN
Js支持参数解构,不需要解构后的值个数和参数个数对应
函数返回值
python 中可以使用 return 1,2 返回多值,本质上也是一个值,就是一个元组。Js中呢
const add = (x, y) => {return x,y}; // ,逗号表达式的值等于最后一个 console.log(add(4,100)); // 100
表达式的值
- 类C的语言,都有一个概念——表达式的值
- 赋值表达式的值:等号右边的值
- 逗号表达式的值:类C语言,都支持逗号表达式,逗号表达式的值,就是最后一个表达式的值
范例-表达式的值
a = ((x = 5), (y = 6), true); console.log(a); //true b = (123, true, (z = "test")); console.log(b); // test function c() { return (x = 5), (y = 6), true, "ok"; } console.log(c()); // ok
所以,JS的函数返回值依然是单值
作用域
// 函数中变量的作用域 function test(){ a = 100; var b = 200; let c = 300; } // 先要运行test函数 test() console.log(a); // console.log(b); // 不可见 // console.log(c); // 不可见
// 块作用域中变量 if (1){ a = 100; var b = 200; let c = 300; } console.log(a); console.log(b); // console.log(c); // 不可见
function是函数的定义,是一个独立的作用域,其中定义的变量在函数外不可见。
var a = 100 可以提升声明,也可以突破非函数的块作用域。
a = 100 隐式声明不能提升声明,在“严格模式”下会出错,但是可以把变量隐式声明为全局变量。建议少用。
let a = 100 不能提升声明,而且不能突破任何的块作用域。推荐使用。
范例-作用域
function show(i, arg) { console.log(i, arg) } // 作用域测试 x = 500; var j = 'jjjj'; var k = 'kkkk'; function fn(){ let z = 400; { var o = 100; // var 作用域当前上下文 show(1, x); // 500 t = 'free'; // 此语句执行后,t作用域就是全局的,不推荐 let p = 200; } var y = 300; show(2,z); // 400 show(3,x); // 500 show(4,o); // 100 show(5,t); // free //show(6,p); // 异常,let出不来上一个语句块 { show(7,y); // 300 show(8,o); // 100 show(9,t); // free { show(10,o); // 100 show(11,t); // free show(12,z); // 400 } } j = 'aaaa'; var k = 'bbbb'; show(20, j); // aaaa show(21, k); // bbbb } // 先执行函数 fn() show(22, j); // aaaa show(23, k); // kkkk //show(13,y); // 异常,y只能存在于定义的上下文中,出不了函数 show(14,t); // free 全局,但是严格模式会抛异常 //show(15,o) // 看不到o,异常原因同y show(16,z); // undefined 变量声明提升,var声明了z,但是此时还没有赋值 var z = 10; const m = 1 //m = 2 // 常量不可以重新赋值
执行结果
1 500 2 400 3 500 4 100 5 free 7 300 8 100 9 free 10 100 11 free 12 400 20 aaaa 21 bbbb 22 aaaa 23 kkkk 14 free 16 undefined
严格模式
使用"use strict";,这条语句放到函数的首行,或者js脚本首行
异常
抛出异常
Js的异常语法和Java相同,使用throw关键字抛出。
使用throw关键字可以抛出任意对象的异常
throw new Error('new error'); throw new ReferenceError('Ref Error'); throw 1; throw 'not ok'; throw [1, 2, 3]; throw {'a':1}; throw () => {}; // 函数
范例
// py: try except finally; raise // js: try catch finally; throw try { console.log('try begin~~~~') // throw new Error('这是个异常') // 抛出异常 throw new ReferenceError('引用异常') // 抛出异常 console.log('try end~~~~') } finally { console.log('fin') }
执行结果
try begin~~~~
fin
D:\project\pyproj\trae\t1.js:7
throw new ReferenceError('这是个异常') // 抛出异常
^
ReferenceError: 这是个异常
at Object.<anonymous> (D:\project\pyproj\trae\t1.js:7:11)
捕获异常
try…catch 语句捕获异常
try…catch…finally 语句捕获异常,finally保证最终一定执行
注意这里的catch不支持类型,也就是说至多一个catch语句
可以在catch的语句块内,自行处理异常
范例-捕获异常
try { console.log('try begin~~~~') // throw new Error('这是个异常') // 抛出异常 throw new ReferenceError('引用异常') // 抛出异常 console.log('try end~~~~') } catch { //这个异常被捕获并匹配上了 console.log('cath U') } finally { console.log('fin') }
执行结果
try begin~~~~ cath U fin
// py: try except finally; raise // js: try catch finally; throw try { console.log('try begin~~~~') // throw new Error('这是个异常') // 抛出异常 throw new ReferenceError('引用异常') // 抛出异常 // throw 1 // 抛出异常 // throw ()=>{} // [] 'abc' 这些都是对象,对象都可以抛出,不建议这么做 console.log('try end~~~~') } catch (err) { //这个异常被捕获并匹配上了 err就是这个异常对象,没有类型 console.log('cath U') console.log(err) console.log(typeof err) // object console.log(err.name) // 异常的名字。 ReferenceError console.log(err.constructor.name) // 每个对象里都会有个构造器,构造器的名字。 ReferenceError console.log(err.message) // 异常的描述。引用异常 console.log(err.stack) // 异常的栈信息,异常的调用栈,异常的调用链,异常的传播路径 } finally { console.log('fin') }
执行结果
try begin~~~~ cath U ReferenceError: 引用异常 at Object.<anonymous> (D:\project\pyproj\trae\t1.js:7:11) at Module._compile (node:internal/modules/cjs/loader:1554:14) at Object..js (node:internal/modules/cjs/loader:1706:10) at Module.load (node:internal/modules/cjs/loader:1289:32) at Function._load (node:internal/modules/cjs/loader:1108:12) at TracingChannel.traceSync (node:diagnostics_channel:322:14) at wrapModuleLoad (node:internal/modules/cjs/loader:220:24) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5) at node:internal/main/run_main_module:36:49 object ReferenceError ReferenceError 引用异常 ReferenceError: 引用异常 at Object.<anonymous> (D:\project\pyproj\trae\t1.js:7:11) at Module._compile (node:internal/modules/cjs/loader:1554:14) at Object..js (node:internal/modules/cjs/loader:1706:10) at Module.load (node:internal/modules/cjs/loader:1289:32) at Function._load (node:internal/modules/cjs/loader:1108:12) at TracingChannel.traceSync (node:diagnostics_channel:322:14) at wrapModuleLoad (node:internal/modules/cjs/loader:220:24) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5) at node:internal/main/run_main_module:36:49 fin
JS对象模型
JavaScript 是一种基于原型(Prototype)的面向对象语言,而不是基于类的面向对象语言。
C++、Java有类Class和实例Instance的概念,类是一类事物的抽象,而实例则是类的实体。
JS是基于原型的语言,它只有原型对象的概念。原型对象就是一个模板,新的对 象从这个模板构建从而获取最初的属性。任何对象在运行时可以动态的增加属性。 而且,任何一个对象都可以作为另一个对象的原型,这样后者就可以共享前者的 属性。
定义类
字面式声明方式
var obj = { property_1: value_1, // property_# 可以是一个标识符... 2: value_2, // 或一个数字... ["property" +3]: value_3, // 或一个可计算的key名... // ..., "property n": value_n // 或一个字符串 };
let a = 1; let b = 'abc'; let c = [1, 2, 3]; let d = x => x * 2; var obj = { 'a': 100, // 引号不省略明确使用该字符串为属性名 b:b, // 引号可以省,但但依然转换为字符串为属性名 [b]:[b], // 计算b的值然后转换为字符串为属性名 3:200, // 将3转换为字符串为属性名 [a]:200, c, //等价于c:c d, //等价于d:d } console.log(obj); console.log(obj.d(10)); // for (let s in obj) // console.log(s, typeof (s), obj[s], typeof (obj[s]));
执行结果
{ '1': 200, '3': 200, a: 100, b: 'abc', abc: [ 'abc' ], c: [ 1, 2, 3 ], d: [Function: d] } 20 1 string 200 number 3 string 200 number a string 100 number b string abc string abc string [ 'abc' ] object c string [ 1, 2, 3 ] object d string [Function: d] function
这种方法也称作字面值创建对象,Js 1.2开始支持。
对象的键key只能是字符串类型,最后都会被转换成字符串。
ES6之前——构造器
1、定义一个函数(构造器)对象,函数名首字母大写
2、使用this定义属性
3、使用new和构造器创建一个新对象
function Point(x, y) { this.x = x; // 对于类的实例来说,this永远指向实例自身 this.y = y; console.log('Point ~~~~') } console.log(Point); let p1 = new Point(4, 5); // 构造出基于Point类型的实例 console.log(p1);
执行结果
[Function: Point] Point ~~~~ Point { x: 4, y: 5 }
function Point(x, y) { this.x = x; // 对于类的实例来说,this永远指向实例自身 this.y = y; console.log('Point ~~~~') this.showme = function(self) {console.log(this, this.x, this.y); console.log(this === self)}; } console.log(Point); let p1 = new Point(4, 5); // new构造出基于Point类型的实例; // 如果没有new就是普通函数Point被调用一次,特别注意this就不是你想要的了 console.log(p1); console.log(p1.showme); p1.showme(p1); console.log('---------------')
执行结果
[Function: Point] Point ~~~~ Point { x: 4, y: 5, showme: [Function (anonymous)] } [Function (anonymous)] Point { x: 4, y: 5, showme: [Function (anonymous)] } 4 5 true ---------------
继承
function Point(x, y) { this.x = x; // 对于类的实例来说,this永远指向实例自身 this.y = y; console.log('Point ~~~~') this.showme = function() {console.log(this, this.x, this.y)}; } function Point3D(x, y, z) { Point.call(this, x, y); // call把当前实例this注入 this.z = z; console.log('Point3D ++++') this.showme = function() {console.log(this, this.x, this.y, this.z)}; // 覆盖 } let p2 = new Point3D(10, 11, 12); console.log(p2); p2.showme();
执行结果
Point ~~~~ Point3D ++++ Point3D { x: 10, y: 11, showme: [Function (anonymous)], z: 12 } Point3D { x: 10, y: 11, showme: [Function (anonymous)], z: 12 } 10 11 12
new 构建一个新的通用对象,new操作符会将新对象的this值传递给Point3D构造器函数,函数为这个对象创建z属性。
从上句话知道,new后得到一个对象,使用这个对象的this来调用构造器,那么如何执行“基类”的构造器方法呢? 使用Point3D对象的this来执行Point的构造器,所以使用call方法,传入子类的this。
最终,构造完成后,将对象赋给p2。
注意:如果不使用new关键字,就是一次普通的函数调用,this不代表实例。
ES6中的class
从ES6开始,新提供了class关键字,使得创建对象更加简单、清晰。
- 类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数
- 一个类只能拥有一个名为constructor的构造器方法。如果没有显式的定义一个构造方法,则会添加一个默认的constuctor方法
- 继承使用extends关键字
- 一个构造器可以使用super关键字来调用一个父类的构造函数
- 类没有私有属性
// 基类定义 class Point { constructor(x, y) /*构造器*/ { this.x = x; this.y = y; } show() /*方法*/ { console.log(this, this.x, this.y); } } let p1 = new Point(10, 11) p1.show() // 继承 class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; } } let p2 = new Point3D(20, 21, 22); p2.show() /* 执行结果 Point { x: 10, y: 11 } 10 11 Point3D { x: 20, y: 21, z: 22 } 20 21 */
class Point{ constructor(x, y) { this.x = x; this.y = y; } show(){ console.log(this.x, this.y); } } console.log(Point, typeof Point); let p1 = new Point(40, 50); console.log(p1) p1.show(); console.log('++++++++++++++') // 子类 class Point3D extends Point{ // 如果自己不实现构造器,默认会提供一个,而且内部使用super的构造器 constructor (x, y, z) { // 子类不写也罢,写了就得用super,不但要写super,还要写在最前面 // Must call super constructor in derived class before accessing 'this' or returning from derived constructor super(x, y); // 新语法通过super调用父类构造器,使用的还是当前的this this.z = z; } } let p2 = new Point3D(10, 11, 12); console.log(p2); p2.show();
执行结果
[class Point] function
Point { x: 40, y: 50 }
40 50
++++++++++++++
Point3D { x: 10, y: 11, z: 12 }
10 11
- 重写方法
子类Point3D的show方法,需要重写
class Point{ constructor(x, y) { this.x = x; this.y = y; } show(){ console.log(this, this.x, this.y); } } console.log(Point, typeof Point); let p1 = new Point(40, 50); console.log(p1) p1.show(); console.log('++++++++++++++') // 子类 class Point3D extends Point{ // 如果自己不实现构造器,默认会提供一个,而且内部使用super的构造器 constructor (x, y, z) { // 子类不写也罢,写了就得用super,不但要写super,还要写在最前面 // Must call super constructor in derived class before accessing 'this' or returning from derived constructor super(x, y); // 新语法通过super调用父类构造器,使用的还是当前的this this.z = z; } show() { // 重写 // console.log('@@@@@@@@@') // super.show(); // console.log(this.z); // console.log('@@@@@@@@@') console.log(this, this.x, this.y, this.z); } } let p2 = new Point3D(10, 11, 12); console.log(p2); p2.show();
执行结果
[class Point] function Point { x: 40, y: 50 } Point { x: 40, y: 50 } 40 50 ++++++++++++++ Point3D { x: 10, y: 11, z: 12 } Point3D { x: 10, y: 11, z: 12 } 10 11 12
子类中直接重写父类的方法即可。
如果需要使用父类的方法,使用super.method()的 方式调用
使用箭头函数重写上面的方法
// 基类定义 class Point { constructor(x, y) /*构造器*/ { this.x = x; this.y = y; //this.show = function () {console.log(this,this.x,this.y)}; this.show = () => console.log('Point'); } } // 继承 class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; this.show = () => console.log('Point3D'); } } let p2 = new Point3D(20, 21, 22); p2.show(); // Point3D
从运行结果来看,箭头函数也支持子类的覆盖
// 基类定义 class Point { constructor(x, y) /*构造器*/ { this.x = x; this.y = y; this.show = () => console.log('Point'); } // show() /*方法*/ { // console.log(this,this.x,this.y); // } } // 继承 class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; //this.show = () => console.log('Point3D'); } show() { // 重写 console.log('Point3D'); } } let p2 = new Point3D(20, 21, 22); p2.show(); // Point
上例优先使用了父类的属性show
// 基类定义 class Point { constructor(x, y) /*构造器*/ { this.x = x; this.y = y; //this.show = () => console.log('Point'); } show() /*方法*/ { console.log(this, this.x, this.y); } } // 继承 class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; this.show = () => console.log('Point3D'); } } let p2 = new Point3D(20, 21, 22); p2.show(); // Point3D
优先使用了子类的属性
总结
- 父类、子类使用同一种方式类定义属性或者方法,子类覆盖父类
- 访问同名属性或方法时,优先使用属性。
- 即,子类如果没有showme属性,就用父类或者自己覆盖的
- 静态属性和静态方法
在方法名前加上static,就是静态方法
class Add { static xyz = 'abcde'; // 静态属性 constructor(x, y) { this.x = x; this.y = y; console.log('Add ~~~~'); } static print() { console.log(this.x, '****', this); //this是谁?Add类自身 // 在class定义的类的静态方法中, this表示Add类自身 console.log(this.xyz); // 在静态方法中只能使用静态属性 } } console.log(Add) Add.print(); // undefined 相当于Python中的类方法 console.log(Add.xyz); // 相当于Python中的类属性 console.log('-'.repeat(30)) let x = new Add(8, 9); console.log(x); console.log(x.print); // 实例不能访问直接访问静态方法,和C++、Java一致 console.log(x.constructor.print); x.constructor.print() // 实例可以通过constructor访问静态方法
执行结果
[class Add] { xyz: 'abcde' } undefined **** [class Add] { xyz: 'abcde' } abcde abcde ------------------------------ Add ~~~~ Add { x: 8, y: 9 } undefined [Function: print] undefined **** [class Add] { xyz: 'abcde' } abcde
静态方法中的this是Add类,而不是Add的实例
注意:静态的概念和Python的静态不同,相当于Python中的类变量
this的坑
虽然Js和 C++ 、Java一样有this,但是Js的表现是不同的。
原因在于, C++ 、Java是静态编译型语言,this是编译期绑定,而Js是动态语言,运行期绑定。
// this 的坑 var school = { name: 'js', getName: function() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return function() { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); console.log(globalThis); // 全局对象 nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this === globalThis); // true console.log(this.name); console.log('++++'); } } } let fn = school.getName(); // 函数定义时 this 指向的是定义时的作用域 fn();
执行结果
~~~~
{ name: 'js', getName: [Function: getName] }
js
~~~~
++++
<ref *1> Object [global] {
global: [Circular *1],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
structuredClone: [Getter/Setter],
atob: [Getter/Setter],
btoa: [Getter/Setter],
performance: [Getter/Setter],
fetch: [Function: fetch],
navigator: [Getter],
crypto: [Getter]
}
true
undefined
++++
function Point(x, y) { this.x = x; this.y = y; console.log('P ++++'); } let p3 = Point(1, 2); //没有new,就是普通函数调用,this指向的是全局对象 // console.log(p3.x); // 异常报错 TypeError: Cannot read properties of undefined (reading 'x') console.log(p3); // undefined 没有返回值,所以是undefined console.log(globalThis.x); // 1 console.log(globalThis.y); // 2
这就是函数调用的时候,调用方式不同,this对应的对象不同,它已经不是C++、Java的指向实例本身了
this的问题,这是历史遗留问题,新版只能保留且兼容了 而我们在使用时,有时候需要明确的让this必须是我们期望的对象,如何解决这个问题呢?
- 1、显式传入
var school = { name: 'js', getName: function() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return function(that) { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); // console.log(globalThis); // nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this === globalThis); // true console.log(that.name); console.log('++++'); } } } let fn = school.getName(); // 函数定义时 this 指向的是定义时的作用域 fn(school); /*执行结果 ~~~~ { name: 'js', getName: [Function: getName] } js ~~~~ ++++ true js ++++ */
通过主动传入对象,这样就避开了this的问题。
严格模式,函数中this是undefined。新版本中建议使用globalThis来统一全局变量。
- 2、ES3(ES-262第三版)引入了apply、call方法
- apply、call方法都是函数对象的方法,第一参数都是传入对象引入的
- apply传其他参数需要使用数组
- call传其他参数需要使用可变参数收集
var school = { name: 'js', getName: function() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return function(t1, t2) { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); // console.log(globalThis); // nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this === globalThis); // true console.log(this.name); console.log(t1, t2); console.log('++++'); } } } // 1 that // let fn = school.getName(); // fn(school); // 2 ES6 call apply 函数对象拥有的2个方法 let fn = school.getName(); fn.call(school, 101, 102); // call方法修改当前对象this指针,后面参数是剩余变量 fn.apply(school, [200, 205]); // apply修改this,后面参数不是剩余变量,把所有实参放到数组中传入
执行结果
~~~~ { name: 'js', getName: [Function: getName] } js ~~~~ ++++ false js 101 102 ++++ ++++ false js 200 205 ++++
call的理解
function Point(x, y) { this.x = x; this.y = y; console.log('P ++++'); } let t1 = new Object(); console.log(t1); // {} let t2 = Point.call(t1, 7, 8); console.log(t1); // { x: 7, y: 8 } t1对象的属性被改变了 console.log(t2); // undefined 调用的返回值是undefined
- 3、ES5 引入了bind方法
// this 的坑 var school = { name: 'js', getName: function() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return function(t1, t2) { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); // console.log(globalThis); // nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this === globalThis); // true console.log(this.name); console.log(t1, t2); console.log('++++'); } } } // 1 that // let fn = school.getName(); // fn(school); // 2 ES6 call apply 函数对象拥有的2个方法 // let fn = school.getName(); // fn.call(school, 101, 102); // call方法修改当前对象this指针,后面参数是剩余变量 // fn.apply(school, [200, 205]); // apply修改this,后面参数不是剩余变量,把所有实参放到数组中传入 // 3 ES5 bind 推荐 let fn = school.getName(); let newfunc = fn.bind(school); // Function.bind(this: Function, thisArg: any, ...argArray: any[]): console.log(newfunc); // 函数,bound 绑定。类似python中partial绑定参数,返回新函数 newfunc(300, 305); newfunc(400, 405);
执行结果
~~~~ { name: 'js', getName: [Function: getName] } js ~~~~ [Function: bound ] ++++ false js 300 305 ++++ ++++ false js 400 405 ++++
apply、call方法,参数不同,调用时传入this bind方法是为函数先绑定this,调用时直接用
- 4、ES6引入支持this的箭头函数
ES6 新技术,就不需要兼容this问题
// this 的坑 var school = { name: 'js', getName: function() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return (t1, t2) => { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); // console.log(globalThis); // nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this === globalThis); //false console.log(this.name); console.log(t1, t2); console.log('++++'); } } } // 1 that // let fn = school.getName(); // fn(school); // 2 ES6 call apply 函数对象拥有的2个方法 // let fn = school.getName(); // fn.call(school, 101, 102); // call方法修改当前对象this指针,后面参数是剩余变量 // fn.apply(school, [200, 205]); // apply修改this,后面参数不是剩余变量,把所有实参放到数组中传入 // 3 ES5 bind 推荐 // let fn = school.getName(); // let newfunc = fn.bind(school); // Function.bind(this: Function, thisArg: any, ...argArray: any[]): // console.log(newfunc); // 函数,bound 绑定。类似python中partial绑定参数,返回新函数 // newfunc(300, 305); // newfunc(400, 405); // 4 ES6 箭头函数. 新语法和老语法在this上处理不同 school.getName()(500, 505);
执行结果
~~~~ { name: 'js', getName: [Function: getName] } js ~~~~ ++++ false js 500 505 ++++
以上解决this问题的方法,bind方法最常用
- 5、使用类
// this 的坑 class School { constructor() { this.name = 'js'; } getName() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return () => { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); console.log(this === globalThis); //nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this.name); console.log('++++'); } } } new School().getName()(); class School2 { constructor() { this.name = 'js'; } getName() { console.log('~~~~'); console.log(this); console.log(this.name); console.log('~~~~'); return function() { // 普通函数在内部使用this的话,往往就是global对象 console.log('++++'); console.log(this === globalThis); //nodejs 中叫golabal; 浏览器中 window; ES6 globalThis console.log(this.name); console.log('++++'); } } } // new School2().getName()();// 报错 TypeError: Cannot read properties of undefined (reading 'name') var school = {name: 'aaaa'}; new School2().getName().bind(school)();
执行结果
~~~~ School { name: 'js' } js ~~~~ ++++ false js ++++ ~~~~ School2 { name: 'js' } js ~~~~ ++++ false aaaa ++++
HTML
CSS
JS2
JS-解构及数组对象操作
JS的解构很灵活,参考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
列表解构
var parts = ['shoulder', 'knees']; var lyrics = ['head', ...parts, 'and', 'toes']; // 使用...解构 console.log(lyrics) // [ 'head', 'shoulder', 'knees', 'and', 'toes' ]
参数解构
let arr = [1, 2] let arr1 = [...arr] function f(x, y, z) { console.log(x + y + z) } var args = [2, 3, 4]; f(...arr); // NaN f(...args); // 9 f(...['a', 'b']); // abundefined
数组解构
JS的数组解构非常强大
let arr = [10,20,30]; // let a,b,c = arr; //逗号表达式优化级最低。 c被赋值为数组 let [x,y,z] = arr; console.log(x, y, z); //10 20 30 // 丢弃变量 const [,b,] = arr; console.log(2,b); //2 20 //b = 5 // 异常,b声明为const // 少于数组元素 const [d,e] = arr; console.log(3,d,e); //3 10 20 // 多于数组元素 const [m,n,o,p] = arr console.log(4,m,n,o,p); //4 10 20 30 undefined // 可变变量 const [f,...args] = arr console.log(5,f); //5 10 console.log(5,args); //5 [20,30] // 支持默认值 const [j=1,k,,,l=10] = arr console.log(j,k,l); //10 20 10
解构的时候,变量从左到右和元素对齐,可变参数放到最右边。
能对应到数据就返回数据,对应不到数据的返回默认值,如果没有默认值返回undefined
对象解构
简单对象解构,非常有用
let obj = { a:100, b:200, c:300, e: [1,2,3] } let {x, y, z} = obj; console.log(x,y,z); //undefined undefined undefined 它的key对应不上,所以是undefined const {a, b, c} = obj; console.log(a,b,c); //100 200 300 它的key对应上了,所以是100 200 300 var {a: m, b: n, c:cc} = obj; // 匹配obj中的key做别名。重命名 console.log(m, n, cc); // 100 200 300 var {a: M, c: N, d: D = 'python'} = obj; //obj中没有d, 给d做别名并缺省值 console.log(M, N, D); // 100 300 'python' const {e:[,f]} = obj; console.log(f); //2 数组解构赋值
解构时,需要提供对象的属性名,会根据属性名找到对应的值。没有找到的返回缺省值,没有缺省值则返回undefined
复杂解构
嵌套数组
const arr = [1, [2, 3], 4]; const [a, [b, c], d] = arr; // 一一对应 console.log(a, b, c, d); //1 2 3 4 const [e, f] = arr; console.log(e, f); //1 [ 2, 3 ] const [g, h, i, j = 18] = arr; console.log(g, h, i, j); //1 [ 2, 3 ] 4 18 const [k, ...l] = arr; //剩余必需在最后 console.log(k, l); //1 [ [ 2, 3 ], 4 ]
对象
var data = { a: 100, b: [ { c: 200, d: [], a: 300 }, { c: 1200, d: [1], a: 1300 }, ], c: 500 } // 提取3个a出来 var { a: m, b: [{ a: n }, { a: n1 }] } = data; console.log(m, n, n1) // 100 300 1300
数组的操作
方法 | 描述 |
push(…items) | 尾部增加多个元素 |
pop() | 移除最后一个元素,并返回它 |
map | 引入处理函数来处理数组中每一个元素,返回新的数组 |
filter | 引入处理函数处理数组中每一个元素,此处理函数返回true的元素保留, |
否则该元素被过滤掉,保留的元素构成新的数组返回 | |
foreach | 迭代所有元素,无返回值 |
const arr = [1, 2, 3, 4, 5]; // map filter 遍历 push //c for index // for in index // for of value arr.push(6, 7); console.log(arr); // [ 1, 2, 3, 4, 5, 6, 7 ] arr.pop() // 删除最后一个元素 -1 pop console.log(arr); // [ 1, 2, 3, 4, 5, 6 ] // map const powerArr = arr.map(x => x * x); // 返回一个新数组,原数组不变 console.log(powerArr); // [ 1, 4, 9, 16, 25, 36 ] // filter 只做判断 console.log(arr.filter(x => x % 2 == 0)) // 新数组 [ 2, 4, 6 ] // forEach const newarr = arr.forEach(x => x + 10); // 无返回值 console.log(newarr, arr); // undefined [ 1, 2, 3, 4, 5, 6 ] narr = [] arr.forEach(x => narr.push(x + 10)); //可以将数据push到新数组narr中 console.log(narr); // [ 11, 12, 13, 14, 15, 16 ] //forEcho((value, index, arr) => {}) //forEach用索引, 第一个参数是值 第二个参数是索引 arr.forEach((value, index, t) => { console.log(value, index, t); // 1 0 [ 1, 2, 3, 4, 5, 6 ] 2 1 [ 1, 2, 3, 4, 5, 6 ] ... narr[index] = arr[index] + 10; }) console.log(narr); //[ 11, 12, 13, 14, 15, 16 ]
数组练习
有一个数组 const arr = [1, 2, 3, 4, 5]; ,要求算出所有元素平方值,且输出平方值是偶数且大于10的平方值
效率偏差
const arr = [1, 2, 3, 4, 5] console.log(arr.map(x => x * x).filter(x => x % 2 === 0).filter(x => x > 10));
应该先过滤,再求值比较好
const arr = [1, 2, 3, 4, 5] // 1 console.log(arr.filter(x => x % 2 === 0).map(x => x * x).filter(x => x > 10)); // 先过滤减少迭代次数 // 2 s = Math.sqrt(10) // 10开方算一次 console.log(arr.filter(x => x > s && !(x % 2)).map(x => x * x)) console.log(arr.filter(x => x > s && !(x & 1)).map(x => x * x)) // 3 let newarr = [] arr.forEach(x => { if (x > s && !(x & 1)) newarr.push(x * x); }) console.log(newarr);
对象的操作
Object的静态方法 | 描述 |
Object.keys(obj) | ES5开始支持。返回所有key |
Object.values(obj) | 返回所有值,试验阶段,支持较差 |
Object.entries(obj) | 返回所有值,试验阶段,支持较差 |
Object.assign(target, …sources) | 使用多个source对象,来填充target对象,返回target对象 |
const obj = { a: 100, b: 200, c: 300 }; console.log(Object.keys(obj)); // key,ES5 console.log(Object.values(obj)); // 值,实验性 console.log(Object.entries(obj)); // 键值对,实验性
assign
let o1 = Object.assign({}, obj); //是不是同一对象 console.log(o1 === obj); //false console.log(o1); //{ a: 100, b: 200, c: [ 1, 2, 3 ] } console.log(obj); //{ a: 100, b: 200, c: [ 1, 2, 3 ] } let o2 = Object.assign({}, obj, {a:1000,b:'abc'}, {d:[100,200,300]}/*新增*/); console.log(o2) //{ a: 1000, b: 'abc', c: [ 1, 2, 3 ], d: [ 100, 200, 300 ] } //这里有浅拷贝吗?有 obj.c[1] = 'abc'; console.log(o1) // { a: 100, b: 200, c: [ 1, 'abc', 3 ] } console.log(o2) // { a: 1000, b: 'abc', c: [ 1, 'abc', 3 ], d: [ 100, 200, 300 ] } console.log(obj) //{ a: 100, b: 200, c: [ 1, 'abc', 3 ] }
包装一个对象
let o3 = new Object(obj); // 同一对象 console.log(obj === o3); //true console.log(obj == o3); //true obj.c[1] = 500; console.log(o3.c); //[1,500,3] obj.a = 10000; console.log(obj === o3); //true
浅拷贝是普通存在的。
JS – Promise对象
概念
ES6开始支持。
Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。
简单说,就是处理异步请求的。
之所以叫做Promise,就是我承诺做这件事,如果成功则怎么处理,失败则怎么处理。
// 语法 new Promise( /* 下面定义的函数是executor */ function(resolve, reject) {...} );
范例:快速理解Promise
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 /** 参数说明 * resolve, reject 这是形参,代表未来有js引擎提供注入2个对应的函数 * resolve 单参函数;reject 单参函数 * 这两个函数互斥,用了其中一个,另一个再用也没有任何用处 * 执行器是一个程序员写好的函数,内部先交给Promise做的事情,最后一定要选择resolve或者reject中的一个调用一下 * 调用时他们都是单参函数,resolve(value)调用返回fulfiled状态表示成功; reject(reason)调用返回rejected状态表示失败 * 如果executor执行时抛出异常,promise返回rejected状态 * 创建Promise会立即执行executor */ console.log("executor 执行了"); // 这个地方有很多代码,大约需要n秒时间 // try{}catch{} // resolve("成功了"); reject('任务失败了'); // throw new Error('wrong wrong wrong'); console.log("executor bye~~~"); }); console.log(p1, '++++++++++++++++') // 类似于python __str__ 表达 // Promise { '成功了' } 等价于 Promise { <fulfiled> '成功了' } 尖括号中是状态,fulfiled 表示成功,rejected 表示失败 // Promise { <rejected> '任务失败了' }
executor
- executor 是一个带有 resolve 和 reject 两个参数的函数。
- executor 函数在Promise构造函数执行时立即执行,被传递resolve和reject函数(executor 函数在Promise构造函数返回新建对象前被调用)。
- executor 内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulfilled即完成,或者在发生错误时将它的状态改为rejected即失败。
- 如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
- executor中,resolve或reject只能执行其中一个函数
Promise的状态
- pending: 初始状态,不是成功或失败状态
- fulfilled: 意味着操作成功完成
- rejected: 意味着操作失败
setInterval(function[, delay]); // 间隔多少毫秒就执行函数一次,循环执行 setTimeout(function[, delay]); // 等待多少毫秒就执行函数一次,结束 delay // 延时,缺省0,立即执行 function // 延时到的时候执行的函数
范例
setInterval(() => { console.log("+++++") }, 3000) console.log("abc") setInterval(() => { console.log("~~~~") }, 3000) console.log("xyz") /* 执行结果:有并行效果 abc xyz +++++ ~~~~ +++++ ~~~~ +++++ ~~~~ */
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 /** 参数说明 * resolve, reject 这是形参,代表未来有js引擎提供注入2个对应的函数 * resolve 单参函数;reject 单参函数 * 这两个函数互斥,用了其中一个,另一个再用也没有任何用处 * 执行器是一个程序员写好的函数,内部先交给Promise做的事情,最后一定要选择resolve或者reject中的一个调用一下 * 调用时他们都是单参函数,resolve(value)调用返回fulfiled状态表示成功; reject(reason)调用返回rejected状态表示失败 * 如果executor执行时抛出异常,promise返回rejected状态 * 创建Promise会立即执行executor */ console.log("executor 执行了"); setTimeout(() => { console.log('3s pass'); resolve('sucess'); //成功了 }, 3000); //不阻塞,立即返回 console.log("executor bye~~~"); }); // Promise { '成功了' } 等价于 Promise { <fulfiled> '成功了' } 尖括号中是状态,fulfiled 表示成功,rejected 表示失败 // Promise { <rejected> '任务失败了' } // Promise { <pending> } 准备着报告结果,正在执行中 setInterval(() =>{ console.log(p1, '++++'); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ Promise { <pending> } ++++ Promise { <pending> } ++++ 3s pass Promise { 'sucess' } ++++ Promise { 'sucess' } ++++ Promise { 'sucess' } ++++ */
Promise.then(onFulfilled, onRejected)
then的返回值是一个新的promise对象。参数是2个函数,根据当前Promise对象 的状态来调用不同的函数,fulfilled走onFulfilled函数,rejected走 onRejected函数。
范例
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 /** 参数说明 * resolve, reject 这是形参,代表未来有js引擎提供注入2个对应的函数 * resolve 单参函数;reject 单参函数 * 这两个函数互斥,用了其中一个,另一个再用也没有任何用处 * 执行器是一个程序员写好的函数,内部先交给Promise做的事情,最后一定要选择resolve或者reject中的一个调用一下 * 调用时他们都是单参函数,resolve(value)调用返回fulfiled状态表示成功; reject(reason)调用返回rejected状态表示失败 * 如果executor执行时抛出异常,promise返回rejected状态 * 创建Promise会立即执行executor * Promise { '成功了' } 等价于 Promise { <fulfiled> '成功了' } Promise { <rejected> '任务失败了' } Promise { <pending> } 准备着报告结果,正在执行中 */ console.log("executor 执行了"); setTimeout(() => { console.log('3s pass'); // resolve('成功了'); reject('失败了'); }, 3000); //不阻塞,立即返回 console.log("executor bye~~~"); }); /* then只是设置回调函数,立即返回,异步的 * then返回一个新Promise对象,p1执行结束了,返回fulfiled或者rejected状态了 */ let p2 = p1.then( value => {console.log('成功回调执行', value); return 1111;}, // 函数,处理fulfiled状态 reason => {console.log('失败回调执行', reason); return 2222}, // 函数,处理rejected状态 ) setInterval(() =>{ console.log(p1, '++++'); // __str__ 表达 console.log(p2, '----'); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ Promise { <pending> } ++++ Promise { <pending> } ---- Promise { <pending> } ++++ Promise { <pending> } ---- 3s pass 失败回调执行 失败了 Promise { <rejected> '失败了' } ++++ Promise { 2222 } ---- Promise { <rejected> '失败了' } ++++ Promise { 2222 } ---- Promise { <rejected> '失败了' } ++++ Promise { 2222 } ---- */
catch(onRejected)
为当前Promise对象添加一个拒绝回调onRejected函数,返回一个新的Promise对象.
Promise 提供2个方法:
- Promise.resolve(value) 返回 状态为fulfilled的Promise对象
- Promise.reject(reason)返回 状态为rejected状态的Promise对象
原理实验
都不处理,then不传递参数
//.... let p2 = p1.then( // undefined, undefined 在then中没有对p1的成功或失败的处理函数定义,p1成功就是p2成功,p2的状态和value和p1一样 // value => {console.log('成功回调执行', value); return 1111;}, // 函数,处理fulfiled状态 // reason => {console.log('失败回调执行', reason); return 2222}, // 函数,处理rejected状态 ) setInterval(() =>{ console.log('p1 +++', p1); // __str__ 表达 console.log('p2 ---', p2); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ p1 +++ Promise { <pending> } p2 --- Promise { <pending> } p1 +++ Promise { <pending> } p2 --- Promise { <pending> } 3s pass p1 +++ Promise { '成功了' } p2 --- Promise { '成功了' } p1 +++ Promise { '成功了' } */
处理
then没有成功参数,只有失败
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 console.log("executor 执行了"); setTimeout(() => { console.log('3s pass'); // resolve('成功了'); reject('失败了'); }, 3000); //不阻塞,立即返回 console.log("executor bye~~~"); }); /* then只是设置回调函数,立即返回,异步的 * then返回一个新Promise对象,p1执行结束了,返回fulfiled或者rejected状态了 * 在成功或失败的架设函数中,返回值很重要。如果没有写就是return undefined,等价于resolve(undefined) */ let p2 = p1.then( // 1 undefined, undefined 在then中没有对p1的成功或失败的处理函数定义,p1成功就是p2成功,p2的状态和value和p1一样 // 2 undefined, reason =>{} 失败定义 p1成功则p2成功;p1失败则p2成功了,return Promise.resolve(undefined) // value => {console.log('成功回调执行', value); return 1111;}, // 函数,处理fulfiled状态 undefined, reason => {console.log('失败回调执行', reason); return Promise.resolve(undefined)}, // 函数,处理rejected状态 ) setInterval(() =>{ console.log('p1 +++', p1); // __str__ 表达 console.log('p2 ---', p2); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ p1 +++ Promise { <pending> } p2 --- Promise { <pending> } p1 +++ Promise { <pending> } p2 --- Promise { <pending> } 3s pass 失败回调执行 失败了 p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } */
then 只有成功没有失败参数
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 console.log("executor 执行了"); setTimeout(() => { console.log('3s pass'); // resolve('成功了'); reject('失败了'); }, 3000); //不阻塞,立即返回 console.log("executor bye~~~"); }); /* then只是设置回调函数,立即返回,异步的 * then返回一个新Promise对象,p1执行结束了,返回fulfiled或者rejected状态了 * 在成功或失败的架设函数中,返回值很重要。如果没有写就是return undefined,等价于resolve(undefined) */ let p2 = p1.then( // 1 undefined, undefined 在then中没有对p1的成功或失败的处理函数定义,p1成功就是p2成功,p2的状态和value和p1一样 // 2 undefined, reason =>{} 失败定义 p1成功则p2成功;p1失败则p2成功了,return Promise.resolve(undefined0) // 2 value =>{}, undefined 成功定义,p1成功则p2 resolve(undefined); p1失败不处理,则p2跟随p1状态 // undefined, // reason => {console.log('失败回调执行', reason); return Promise.resolve(undefined)}, // 函数,处理rejected状态 value => {console.log('成功回调执行', value); return undefined;}, // , undefined ).catch( reason => {console.log('失败回调执行', reason); return Promise.resolve(undefined)} // 函数,处理rejected状态 ) setInterval(() =>{ console.log('p1 +++', p1); // __str__ 表达 console.log('p2 ---', p2); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ p1 +++ Promise { <pending> } p2 --- Promise { <pending> } p1 +++ Promise { <pending> } p2 --- Promise { <pending> } 3s pass 失败回调执行 失败了 p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } */
都处理
let p1 = new Promise((resolve, reject) => { //成功有成功的结果,失败有失败的理由 console.log("executor 执行了"); setTimeout(() => { console.log('3s pass'); // resolve('成功了'); reject('失败了'); }, 3000); //不阻塞,立即返回 console.log("executor bye~~~"); }); /* then只是设置回调函数,立即返回,异步的 * then返回一个新Promise对象,p1执行结束了,返回fulfiled或者rejected状态了 * 在成功或失败的架设函数中,返回值很重要。如果没有写就是return undefined,等价于resolve(undefined) */ let p2 = p1.then( // 1 undefined, undefined 在then中没有对p1的成功或失败的处理函数定义,p1成功就是p2成功,p2的状态和value和p1一样 // 2 undefined, reason =>{} 失败定义 p1成功则p2成功;p1失败则p2成功了,return Promise.resolve(undefined0) // 2 value =>{}, undefined 成功定义,p1成功则p2 resolve(undefined); p1失败不处理,则p2跟随p1状态 // undefined, // reason => {console.log('失败回调执行', reason); return Promise.resolve(undefined)}, // 函数,处理rejected状态 // value => {console.log('成功回调执行', value); return Promise.reject(777);}, // , undefined // 3 value =>{},reason =>{} value => {console.log('成功回调执行', value); // return Promise.resolve(undefined) //默认调用 // return Promise.reject(777) }, reason => {console.log('失败回调执行', reason); // return Promise.resolve(undefined) //默认调用 } ) setInterval(() =>{ console.log('p1 +++', p1); // __str__ 表达 console.log('p2 ---', p2); // __str__ 表达 }, 1000) // 每隔1s 执行一次 /* 执行结果: executor 执行了 executor bye~~~ p1 +++ Promise { <pending> } p2 --- Promise { <pending> } p1 +++ Promise { <pending> } p2 --- Promise { <pending> } 3s pass 失败回调执行 失败了 p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } p1 +++ Promise { <rejected> '失败了' } p2 --- Promise { undefined } */
实例:链式调用
var myPromise = new Promise(function(resolve, reject){ console.log('do sth.') setTimeout(()=>{ console.log('~~~~~') resolve('ok'); //reject('error'); }, 3000); // 延时3秒执行一次结束 }) let pro1 = myPromise.then( value => {/*如果成功则显示结果*/ console.log(1, 'successful'); return 1111; }, reason => {/*如果失败则显示原因*/ console.log(2, 'failed'); return 2222; } ) let pro2 = myPromise.catch(reason=>{ console.log(3, reason) }) // 开始链式调用 pro2.then( value => console.log(4, value), // value是什么? reason => console.log(5, reason) // reason是什么? ).then( value => { console.log(6, value) // 已经不是pro2对象了,value是什么 return Promise.reject('pro2 => new Promise object rejected'); } ).catch( reason => { console.log(7, reason); return Promise.resolve(reason + ' *') } ).then( value => console.log(8, value), // value是什么? reason => console.log(9, reason) // reason是什么? ) // 返回的是什么? /*执行结果 do sth. ~~~~~ 1 'successful' 4 'ok' 6 undefined 7 'pro2 => new Promise object rejected' 8 'pro2 => new Promise object rejected *' */
异步实例
function runAsync() { return new Promise(function (resolve, reject) { // 异步操作 setTimeout(function () { console.log('do sth...'); resolve('ok...'); }, 3000); }); } // 调用 runAsync().then( value => { console.log(value); return Promise.reject(value + '*'); } ).catch( reason => { console.log(reason); return Promise.resolve(reason + '*'); } ).then( value => { console.log(value); console.log('Promise END'); } ) console.log('==== FIN ===='); /*执行结果 ==== FIN ==== do sth... ok... ok...* ok...** Promise END */
JS-高阶对象、高阶类或称Mixin模式
Mixin模式,混合模式。
JS是基于对象的,类和对象都是对象模板。
混合mixin,指的是将一个对象的全部或者部分拷贝到另一个对象上去。其实就是属性。
可以将多个类或对象混合成一个类或对象。
继承实现
继承:子子孙孙都有基类的功能
class Serialization { constructor() { console.log('Serialization constructor~~~'); if (typeof (this.stringify) !== 'function') { //可以对对象做增强 throw new ReferenceError('should define stringify.'); } } } class Point extends Serialization { constructor(x, y) { console.log('Point Constructor~~~~'); super(); // 调用父构造器 this.x = x; this.y = y; } } //s = new Serialization(); // 构造Serialization失败 //p = new Point(4,5); // 构造子类对象时,调用父类构造器执行也会失败
父类构造函数中,要求具有属性是stringify的序列化函数,如果没有则抛出异常
完整继承的代码
class Serialization { constructor() { console.log('Serialization constructor~~~'); if (typeof (this.stringify) !== 'function') { throw new ReferenceError('should define stringify.'); } } } class Point extends Serialization { constructor(x, y) { console.log('Point Constructor~~~~'); super(); // 调用父构造器 this.x = x; this.y = y; } stringify() { return `<Point x=${this.x}, y=${this.y}>` } } class Point3D extends Point { constructor(x, y, z) { super(x, y); this.z = z; } stringify() { return `<Point x=${this.x}, y=${this.y}, z=${this.z}>` } } p = new Point(4, 5); console.log(p.stringify()) p3d = new Point3D(7, 8, 9); console.log(p3d.stringify()); /* 执行结果 Point Constructor~~~~ Serialization constructor~~~ <Point x=4, y=5> Point Constructor~~~~ Serialization constructor~~~ <Point x=7, y=8, z=9> */
高阶对象实现
将类的继承构建成箭头函数
// 普通的继承 class A extends Object { }; console.log(A);
// 匿名类 const A1 = class { constructor(x) { this.x = x; } } console.log(A1); console.log(new A1(100).x);
// 匿名继承 const B = class extends Object { constructor() { super(); console.log('B constructor'); } }; console.log(B); b = new B(); console.log(b);
/ 箭头函数,参数是类,返回值也是类 / 把上例中的Object看成参数 const x = (Sup) => { return class extends Sup { constructor() { super(); console.log('C constructor'); } }; }
// 演化成下面的形式 const C = Sup => class extends Sup { constructor() { super(); console.log('C constructor'); } };
/cls = new C(Object); / 不可以new,因为C是一个普通函数,它的返回值是一个带constructor的类 cls = C(A); // 调用它返回一个类,一个带constructor的class console.log(cls); c = new cls(); console.log(c);
/ 其它写法 c1 = new (C(Object))(); / new优先级太高了,所有后面要加括号才能先调用
* 执行结果 [Function: A] [Function: A1] 100 [Function: B] B constructor B {} [Function: A] C constructor A {} C constructor *
说到底,上面的C这个函数,本质上就是传入一个基类,然后通过基类继承构造一个新的类。
// class A {} // class B extends Object {} // const C = class D {} // console.log(A, B, C); // const E = class extends Object {} // function fn() { // // let E = class extends Object { // // show() { // // console.log('****'); // // } // // } // // return E; // return class extends Object { // show() { // console.log('****'); // } // } // } // let Cls = fn(); // console.log(Cls); // new Cls().show(); // // new (fn())().show(); // /* 执行结果 // [class (anonymous) extends Object] // **** // */ // function fn(X=Object) { // return class extends X { // show() { // console.log('****'); // } // } // } // console.log(fn); // let Cls = fn(Object); // console.log(Cls); // /* 执行结果 // [Function: fn] // [class (anonymous) extends Object] // */ // 变化一下 const wrapper = SupCls => { // 返回新的子类 return class extends SupCls { show() { console.log('****'); } } } // function wrapper(Supcls) { // return class extends Supcls {} // 新的子类 // } // 1. 形参 传入的是什么类型 类 // 2. 返回值,返回什么类型 类,新的子类 // 高阶类,高阶对象 console.log(wrapper); let Cls = wrapper(Object); console.log(Cls); new Cls().show(); /* 执行结果 [Function: wrapper] [class (anonymous) extends Object] **** */
Mixin类
缺什么能力 补什么能力
可以改造上面序列化的例子
const SerializationMixin = Sup => class extends Sup { constructor(...args) { console.log('SerializationMixin constructor~~~'); super(...args); if (typeof (this.stringify) !== 'function') { throw new ReferenceError('should define stringify.'); } } } class Point { constructor(x, y) { console.log('Point Constructor~~~~'); this.x = x; this.y = y; } } class Point3D extends SerializationMixin(Point) { constructor(x, y, z) { super(x, y); // super是Serialization(Point)包装过的新类型 this.z = z; } stringify() { return `<Point3D ${this.x}.${this.y}.${this.z}>`; } } let p3d = new Point3D(70, 80, 90); console.log(p3d.stringify()); /*执行结果 SerializationMixin constructor~~~ Point Constructor~~~~ <Point3D 70.80.90> */
注意:
Serialization(Point)这一步实际上是一个匿名箭头函数调用,返回了一个新的类型,Point3D继承自这个新的匿名类型,增强了功能。
React框架大量使用了这种Mixin技术。
Serialization(Point)传入的参数是类,返回值也是一个类,这就是高阶类。
本质上就是把一个类包装后返回一个增强的包装供其他类使用。
JS – 模块化(babel转译工具)
概念
ES6之前,JS没有出现模块化系统。因为它在设计之初根本没有想到今天的JS应用场景。
JS主要在前端的浏览器中使用,js文件下载缓存到客户端,在浏览器中执行。
比如简单的表单本地验证,漂浮一个广告。
服务器端使用ASP、JSP等动态网页技术,将动态生成数据嵌入一个HTML模板,里 面夹杂着JS后使用 <script> 标签,返回浏览器端执行。 <script> 还可以使用 src属性,发起一个GET请求返回一个js文件,嵌入到当前页面执行环境中执行。
这时候的JS只是一些简单函数和语句的组合。
2005年之后,随着Google大量使用了AJAX技术之后,可以异步请求服务器端数据,带来了前端交互的巨大变化。 前端功能需求越来越多,代码也越来也多。随着js文件的增多,灾难性的后果产生了:
- 众多js文件通过 <script> 引入到当前页面中,每一个js文件发起一个GET请求,众多的js文件都需要返回到浏览器端。网络开销成本颇高
- 习惯了随便写,js脚本中各种 全局变量污染 ,函数名冲突
- JS脚本加载有顺序,JS文件中的代码之间的依赖关系(依赖前后顺序、相互依赖)
亟待模块化的出现。
2008年V8引擎发布,2009年诞生了Nodejs,支持服务端JS编程。使用JS编程的项目规模越来越大,没有模块化是不可想像的。
之后社区中诞生诸多模块化解决方案。
CommonJS规范(2009年),使用全局require函数导入模块,将所有对象约束在模块对象内部,使用exports导出指定的对象。
最早这种规范是用在Nodejs后端的,后来又向前端开发移植,这样浏览器端开发也可以使用CommonJS了。
AMD(Asynchronous Module Definition)异步模块定义,这是由社区提出的一 种浏览器端模块化标准。使用异步方式加载模块,模块的加载不影响它后面语句 的执行。所有依赖这个模块的语句,都需要定义在一个回调函数,回调函数中使 用模块的变量和函数,等模块加载完成之后,这个回调函数才会执行,就可以安 全的使用模块的资源了。其实现就是AMD/RequireJs。AMD虽然是异步,但是会预 先加载和执行。目前应用较少。
CMD(Common Module Definition),使用seajs,作者是淘宝前端玉伯,兼容并包解决了RequireJs的问题 CMD推崇as lazy as possible,尽可能的懒加载。
由于社区的模块化呼声很高,ES6开始提供支持模块的语法,但是浏览器目前支持还不够。
ES6模块化
ES6中模块自动采用严格模式。
- import语句,导入另一个模块导出的绑定。
- export语句,从模块中导出函数、对象、值的,供其它模块import导入用。
导出
建立一个模块目录src,然后在这个目录下新建一个moda.js,内容如下:
// 缺省导出 export default class A{ constructor(x){ this.x = x; } show(){ console.log(this.x); } } // 导出函数 export function foo(){ console.log('foo function'); } // 导出常量 export const CONSTA = 'aaa'
导入
其它模块中导入语句如下
import {CONSTA, foo} from "./moda"; import * as mod_a from "./moda";
VS Code可以很好的语法支持了,但是运行环境,包括V8引擎,都不能很好的支持模块化语法
转译工具
转译就是从一种语言代码转换到另一个语言代码,当然也可以从高版本转译到低版本的支持语句。
由于JS存在不同版本,不同浏览器兼容的问题,如何解决对语法的支持问题?
使用transpiler转译工具解决对语法的支持问题。
babel
开发中可以使用较新的ES6语法,通过转译器转换为指定的某些版本代码。
参考7.x文档 https://babeljs.io/docs/
打开Try it out,测试一段代码
function * counter(){ let i = 0; while(true) yield (++i); } g = counter(); console.log(g.next().value);
预设
有如下一些预设presets,我们先看看有哪些,一会儿再进行预设的安装和配置。
#presets: babel-preset-env 当前环境支持的代码,新target #可以替代下面的几个 #ES2015转码规则 npm install --save-dev babel-preset-es2015 #react转码规则 npm install --save-dev babel-preset-react #ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个 npm install --save-dev babel-preset-stage-0 npm install --save-dev babel-preset-stage-1 npm install --save-dev babel-preset-stage-2 npm install --save-dev babel-preset-stage-3
离线转译安装配置
- 1、初始化npm
在项目目录中使用
#目录结构 D:. ├─lib #输出目录 └─src #源码目录 t1.js t2.js PS D:\project\pyprojs\trae\mypro> npm init Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (mypro) test version: (1.0.0) description: #描述 entry point: (index.js) #入口 test command: #测试命令 git repository: keywords: author: jasper license: (ISC) MIT #许可证 About to write to D:\project\pyprojs\trae\mypro\package.json: { "name": "test", "version": "1.0.0", "main": "index.js", "directories": { "lib": "lib" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "jasper", "license": "MIT", "description": "" } Is this OK? (yes) y #目录结构 D:. │ package.json │ ├─lib └─src t1.js t2.js cat package.json { "name": "test", "version": "1.0.0", "main": "index.js", "directories": { "lib": "lib" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "jasper", "license": "MIT", "description": "" } #package.json 根目录, 这个文件很重要 #先对项目初始化,才可以使用babel
在项目根目录下会生成package.json文件,内容就是上面花括号的内容
- 2、设置镜像
为了连接国内的服务器,当前项目创建一个配置 .npmrc文件
- 可以放在全局配置下,所有人用
- 可以放到用户家目录中,当前用户下所有项目使用
- 可以放到项目根目录中,当前项目用(推荐此种做法)
参考 https://developer.aliyun.com/mirror/NPM
本次放到项目根目录中,内容如下 registry=https://registry.npmmirror.com
#放在当前项目下,优化读取.npmrc内容 echo "registry=https://registry.npmmirror.com" > .npmrc
- 3、安装
项目根目录下执行
#babel7 开发时依赖 npm install --save-dev @babel/core @babel/cli #npm install --save-dev @babel/core @babel/cli @babel/preset-env #对应package.json下添加开发时依赖,"devDependencies": PS D:\project\pyprojs\trae\mypro> npm list test@1.0.0 D:\project\pyprojs\trae\mypro ├── @babel/[email protected] └── @babel/[email protected] #babel6 npm install babel-core babel-cli --save-dev
--save-dev说明 当你为你的模块安装一个依赖模块时,正常情况下你得先安装他们(在模块根目 录下npm install module-name),然后连同版本号手动将他们添加到模块配置 文件package.json中的依赖里(dependencies)。开发用 --save和--save-dev可以省掉你手动修改package.json文件的步骤 spm install module-name --save 自动把模块和版本号添加到dependencies部分。部署运行时用 spm install module-name --save-dev 自动把模块和版本号添加到devdependencies部分 -g 安装全局,非万不得,不要使用 没有加 -g 默认安装到当前项目中
安装完后,在项目根目录下出现 node_modules目录 ,里面有babel相关模块及依赖的模块。
- 4、配置babel和安装预设
#babel7 #在项目的根目录中创建一个名为 babel.config.json { "presets": [ [ "@babel/preset-env", { "targets": { "edge": "17", "firefox": "60", "chrome": "67", "safari": "11.1" }, "useBuiltIns": "usage", "corejs": "3.6.5" } ] ] } #必须针对要支持的浏览器对其进行调整。 #babel6 #在目录根目录下创建 .babelrc 文件,Json格式 { "presets": ["env"] }
env 可以根据当前环境自动选择. 安装依赖
#babel7 npm install --save-dev @babel/preset-env npm install --save core-js #babel6 npm install babel-preset-env --save-dev
然后运行这个命令,把你所有的代码从 src 目录编译到 lib目录:
./node_modules/.bin/babel src --out-dir lib #或者用 npx babel src -d lib
- 5、准备目录
项目根目录下建立src和lib目录
- src 是源码目录
- lib 是目标目录
- public 是静态目录
- 6、修改package.json
替换为 scripts 的部分
{ "name": "test", "version": "1.0.0", "main": "index.js", "directories": { "lib": "lib" }, "scripts": { "build":"babel src -d lib" }, "author": "jasper", "license": "MIT", "description": "", "devDependencies": { "@babel/cli": "^7.27.2", "@babel/core": "^7.27.1", "@babel/preset-env": "^7.27.2" } }
babel src -d lib 意思是从src目录中转译后的文件输出到lib目录
这样就可以在项目根下,用命令执行了。
npm run build #相当于做scripts中的build命令。这里是 babel src -d lib
- 7、准备js文件
在src中的moda.js文件
// 缺省导出 export default class A{ constructor(x){ this.x = x; } show(){ console.log(this.x) } } export function foo(){ console.log('foo function') } export const constV = 1234 export var x = 2345; export let y = 3456;
src目录下新建index.js
import A, {foo, constV, x, y} from './t1.js'; foo() console.log(constV, x, y); let a = new A('abc'); // a.show(); console.log(a.x);
直接在VS Code的环境下执行出错。估计很难有能够正常运行的环境。所以,要转译为ES5的代码。
#在项目根目录下执行命令 npm run build #可以在lib文件夹中看到,2个文件被转译 #运行文件 node lib/index.js
使用babel等转译器转译JS非常流行.
开发者可以在高版本中使用新的语法特性,提高开发效率,把兼容性问题交给转译器处理。
npx是包之心器命令从npm 5.2开始提供。npx可以直接执行已经安装过的包的命令,而不用配置package.json中的run-script。
npx babel src -d lib node lib/index.js
导入导出
导出代码都在src/moda.js中,导入代码都写在src/index.js中
缺省导入导出
只允许一个缺省导出,缺省导出可以是变量、函数、类,但不能使用let、var、const关键字作为默认导出
- 缺省导入的时候,可以自己重新命名,可以不需要和缺省导出时的名称一致,但最好一致.
- 缺省导入,不需要在import后使用花括号
// 缺省导出 匿名函数 export default function(){ console.log('default export function') } // 缺省导入 import defaultFunc from '/.moda' defaultFunc(); // 缺省导出 命名函数 export default function xyz(){ console.log('default export function') } // 缺省导入 import defaultFunc from './moda' defaultFunc();
命名导入导出
导出举例
// 缺省导出类 export default class { constructor(x) { this.x = x; } show() { console.log(this.x); } } // 命名导出 函数 export function foo() { console.log('regular foo()'); } // 函数定义 function bar() { console.log('regular bar()'); } // 变量常量定义 let x = 100; var y = 200; const z = 300; // 导出 export { bar, x, y, z };
导入举例
// as 设置别名 import defaultCls, { foo, bar, x, y, z as CONST_C } from './moda'; foo(); bar(); console.log(x); // x只读,不可修改,x++异常 console.log(y); // y只读 console.log(CONST_C); new defaultCls(1000).show();
也可以使用下面的形式,导入所有导出,但是会定义一个新的名词空间。使用名词空间可以避免冲突
import * as newmod from './mod'; newmod.foo(); newmod.bar(); new newmod.default(2000).show();
范例
// import DefaultCls, {foo, constV as v, x, y} from './t1.js'; // foo() // console.log(v, x, y); // let a = new DefaultCls('abc'); // // a.show(); // console.log(a.x); import * as newMod from './t1.js'; // 导入所有放入对象中 console.log(newMod); console.log(new newMod.default('abc').x) console.log(newMod.x, newMod.y, newMod.constV) newMod.foo() /* 执行结果 npx babel src -d lib; node lib/t2.js { constV: 1234, default: [class A], foo: [Function: foo], x: 2345, y: 3456 } abc 2345 3456 1234 foo function */