Python: WEB开发-JS

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
运算符优先级

js运算符优先级

运算符由高到低,顺序如下

. []
() 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关键字,使得创建对象更加简单、清晰。

  1. 类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数
  2. 一个类只能拥有一个名为constructor的构造器方法。如果没有显式的定义一个构造方法,则会添加一个默认的constuctor方法
  3. 继承使用extends关键字
  4. 一个构造器可以使用super关键字来调用一个父类的构造函数
  5. 类没有私有属性
// 基类定义
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

CSS

JS2

JS-解构及数组对象操作

列表解构

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语法,通过转译器转换为指定的某些版本代码。

官网 http://babeljs.io/

参考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
*/
emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike