语法与数据类型
语法
var\let\const
声明一个变量,可选初始化一个值。
声明一个块作用域的局部变量,可选初始化一个值。
声明一个块作用域的只读常量。
用 var
或 let
语句声明的变量,如果没有赋初始值,则其值为 undefined。如果访问一个未声明的变量会导致抛出 ReferenceError 异常:
undefined
值在布尔类型环境中会被当作false
- 数值类型环境中
undefined
值会被转换为NaN
- 当你对一个 null 变量求值时,空值
null
在数值类型环境中会被当作 0 来对待
变量提升
一个函数中所有的**
var
语句应尽可能地放在接近函数顶部的地方**。
JavaScript 变量的另一个不同寻常的地方是,你可以先使用变量稍后再声明变量而不会引发异常。这一概念称为变量提升;**JavaScript 变量感觉上是被"提升"或移到了函数或语句的最前面。但是,提升后的变量将返回 undefined 值。**因此在使用或引用某个变量之后进行声明和初始化操作,这个被提升的变量仍将返回 undefined 值。
在 ECMAScript 6 中,
let
和const
同样会被提升变量到代码块的顶部但是不会被赋予初始值。在变量声明之前引用这个变量,将抛出引用错误(ReferenceError)。这个变量将从代码块一开始的时候就处在一个"暂时性死区",直到这个变量被声明为止。对于函数来说,只有函数声明会被提升到顶部,而函数表达式不会被提升。
/**
- 例子 2
*/
// will return a value of undefined
var myvar = "my value";(function () {
console.log(myvar); // undefined
var myvar = "local value";
})();console.log(x); // ReferenceError
let x = 3;/* 函数声明 */
foo(); // "bar"
function foo() {
console.log("bar");
}/* 函数表达式 */
baz(); // 类型错误:baz 不是一个函数
var baz = function () {
console.log("bar2");
};
常量
对象属性被赋值为常量是不受保护的,所以下面的语句执行时不会产生错误。
const MY_OBJECT = { key: "value" };
MY_OBJECT.key = "otherValue";
同样的,数组的被定义为常量也是不受保护的,所以下面的语句执行时也不会产生错误。
const MY_ARRAY = ["HTML", "CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); //logs ['HTML','CSS','JAVASCRIPT'];
全局变量
实际上,全局变量是全局对象 的属性。在网页中,(译注:缺省的)全局对象是 window ,所以你可以用形如window
.
variable
的语法来设置和访问全局变量。常量
数据类型
- 七种基本数据类型:
- 布尔值(Boolean),有 2 个值分别是:
true
和false
。- null,一个表明 null 值的特殊关键字。JavaScript 是大小写敏感的,因此
null
与Null
、NULL
或变体完全不同。- undefined,和 null 一样是一个特殊的关键字undefined 表示变量未赋值时的属性。
- 数字(Number),整数或浮点数,例如:
42
或者3.14159
。- 任意精度的整数(BigInt),可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。
- 字符串(String),字符串是一串表示文本值的字符序列,例如:
"Howdy"
。- 代表(Symbol,在 ECMAScript 6 中新添加的类型)。一种实例是唯一且不可改变的数据类型。
- 以及对象**(Object)**。
对象属性名字可以是任意字符串 ,包括空串。如果对象属性名字不是合法的 javascript 标识符,它必须用引号包裹。
const unusualPropertyNames = { '': '空字符串', '!': '砰!' } console.log(unusualPropertyNames.''); // SyntaxError: Unexpected string console.log(unusualPropertyNames.!); // SyntaxError: Unexpected token ! ----------------------------------------------------------------------- console.log(unusualPropertyNames[""]); // 空字符串 console.log(unusualPropertyNames["!"]); // 砰!
!!!核心基础:1.访问对象属性foo['var']最稳妥 2.字符串插值"${var}"
//访问对象属性 var foo = { a: "alpha", 2: "two" }; console.log(foo.a); // alpha console.log(foo[2]); // two //console.log(foo.2); // SyntaxError: missing ) after argument list //console.log(foo[a]); // ReferenceError: a is not defined console.log(foo["a"]); // alpha console.log(foo["2"]); // two ---------------------------------------------------------------------------- //字符串插值"${var}" var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`;
数字-》字符串:"+"
x = "The answer is " + 42; // "The answer is 42" y = 42 + " is the answer"; // "42 is the answer"
字符串-》数字:parseInt()、parseFloat()
parseInt
方法只能返回整数 ,所以使用它会丢失小数部分。另外,调用 parseInt 时最好总是**带上进制(radix)**参数,这个参数用于指定使用哪一种进制。x = "The answer is " + 42; // "The answer is 42" y = 42 + " is the answer"; // "42 is the answer"
进制
整数可以用十进制(基数为 10)、十六进制(基数为 16)、八进制(基数为 8)以及二进制(基数为 2)表示。
- 十进制整数字面量由一串数字序列组成,且没有前缀 0。
- 八进制的整数以 0(或 0O、0o)开头,只能包括数字 0-7。
- 十六进制整数以 0x(或 0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。
- 二进制整数以 0b(或 0B)开头,只能包含数字 0 和 1。
控制语句与错误处理
throw语句
throw "Error2"; // String type
throw 42; // Number type
throw true; // Boolean type
throw {
toString: function () {
return "I'm an object!";
},
};
------------------------------------------
//可以在抛出异常时声明一个对象。那你就可以在 catch 块中查询到对象的属性。
// Create an object type UserException
function UserException(message) {
this.message = message;
this.name = "UserException";
}
// Make the exception convert to a pretty string when used as
// a string (e.g. by the error console)
UserException.prototype.toString = function () {
return this.name + ': "' + this.message + '"';
};
// Create an instance of the object type and throw it
throw new UserException("Value too high");
try....catch...finally
换句话说,如果你在 try 代码块中的代码如果没有执行成功,那么你希望将执行流程转入 catch 代码块。如果 try 代码块没有抛出异常,catch 代码块就会被跳过。
finally
块无论是否抛出异常都会执行。如果抛出了一个异常,就算没有异常处理,finally
块里的语句也会执行。
执行顺序
function f() {
try {
console.log(0);
throw "bogus";
} catch (e) {
console.log(1);
return true; // this return statement is suspended
// until finally block has completed
console.log(2); // not reachable
} finally {
console.log(3);
return false; // overwrites the previous "return"
console.log(4); // not reachable
}
// "return false" is executed now
console.log(5); // not reachable
}
f(); // console 0, 1, 3; returns false
----------------------------------------------------------------------
function f() {
try {
throw "bogus";
} catch (e) {
console.log('caught inner "bogus"');
throw e; // this throw statement is suspended until
// finally block has completed
} finally {
return false; // overwrites the previous "throw"
}
// "return false" is executed now
}
try {
f();
} catch (e) {
// this is never reached because the throw inside
// the catch is overwritten
// by the return in finally
console.log('caught outer "bogus"');
}
// OUTPUT
// caught inner "bogus"
Error对象
DOMException、ECMAScript exceptions
function doSomethingErrorProne () {
if (ourCodeMakesAMistake()) {
throw (new Error('The message'));
} else {
doSomethingToGetAJavascriptError();
}
}
....
try {
doSomethingErrorProne();
}
catch (e) {
console.log(e.name); // logs 'Error'
console.log(e.message); // logs 'The message' or a JavaScript error message)
}
循环与迭代
基本循环语句
//for语句
for (var i = 0; i < selectObject.options.length; i++) {
if (selectObject.options[i].selected) {
numberSelected++;
}
}
---------------------------------------------------------
//do...while语句
var i = 0;
do {
i += 1;
console.log(i);
} while (i < 5);
----------------------------------------------------------
//while语句
var n = 0;
var x = 0;
while (n < 3) {
n++;
x += n;
}
------------------------------------------------------------
break与continue
label语句
一个 label 提供了一个让你在程序中其他位置引用它的标识符。例如,你可以用 label 标识一个循环,然后使用
break
或者continue
来指出程序是否该停止循环还是继续循环。var num = 0; outPoint: for (var i = 0; i < 10; i++) { for (var j = 0; j < 10; j++) { if (i == 5 && j == 5) { break outPoint; // 在 i = 5,j = 5 时,跳出所有循环, // 返回到整个 outPoint 下方,继续执行 } num++; } } alert(num); // 输出 55 var num = 0; outPoint: for (var i = 0; i < 10; i++) { for (var j = 0; j < 10; j++) { if (i == 5 && j == 5) { continue outPoint; } num++; } } alert(num); // 95
for...in语句
for...in 语句循环一个指定的变量来循环一个对象所有可枚举的属性。JavaScript 会为每一个不同的属性执行指定的语句。
虽然使用 for...in 来迭代数组 Array 元素听起来很诱人,但是它返回的东西除了数字索引外,还有可能是你自定义的属性名字 。因此还是用带有数字索引的传统的 for 循环来迭代一个数组比较好,因为,如果你想改变数组对象,比如添加属性或者方法,for...in 语句迭代的是自定义的属性,而不是数组的元素。
function dump_props(obj, obj_name) { var result = ""; for (var i in obj) { result += obj_name + "." + i + " = " + obj[i] + "<br>"; } result += "<hr>"; return result; }
for...of语句
for...of 语句在可迭代对象(包括Array、Map、Set、arguments 等等)上创建了一个循环,对值的每一个独特属性调用一次迭代。
迭代协议: 迭代协议具体分为两个协议:可迭代协议** 和**迭代器协议。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols
let arr = [3, 5, 7]; arr.foo = "hello"; for (let i in arr) { console.log(i); // 输出 "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // 输出 "3", "5", "7" } // 注意 for...of 的输出没有出现 "hello"
函数
定义函数
函数提升仅适用于函数声明 ,而不适用于函数表达式
函数声明
函数表达式
//例子1
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};
console.log(factorial(3)); // 6
//factorial(n)、fac(n)、arguments.callee()
-------------------------------------------
//例子2 该函数接收由函数表达式定义的函数,并对作为第二个参数接收的数组的每个元素执行该函数:
function map(f, a) {
const result = new Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = f(a[i]);
}
return result;
}
const cube = function (x) {
return x * x * x;
};
const numbers = [0, 1, 2, 5, 10];
console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000]
递归
function foo(i) {
if (i < 0) {
return;
}
console.log(`开始:${i}`);
foo(i - 1);
console.log(`结束:${i}`);
}
foo(3);
// 打印:
// 开始:3
// 开始:2
// 开始:1
// 开始:0
// 结束:0
// 结束:1
// 结束:2
// 结束:3
用于获取DOM树子节点
function walkTree(node) {
if (node === null) {
return;
}
// 对节点做些什么
for (let i = 0; i < node.childNodes.length; i++) {
walkTree(node.childNodes[i]);
}
}
闭包
闭包 在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的 。闭包 是可以拥有独立变量以及绑定了这些变量的环境("封闭"了表达式)的表达式 (通常是函数)。既然嵌套函数是一个闭包,就意味着一个嵌套函数可以"继承"容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数具有定义在外部函数中的所有变量和函数 (以及外部函数能访问的所有变量和函数)的完全访问权限。
外部函数却不能访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一种封装。
此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时 ,外部函数中定义的变量和函数的生存周期将比内部函数执行的持续时间要长 。当内部函数以某一种方式被任何一个外部函数之外的任何作用域访问时,就会创建闭包。
- 内部函数只可以在外部函数中访问。
- 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
function addSquares(a, b) {
function square(x) {
return x * x;
}
return square(a) + square(b);
}
console.log(addSquares(2, 3)); // 13
console.log(addSquares(3, 4)); // 25
console.log(addSquares(4, 5)); // 41
--------------------------------------
//由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
const fnInside = outside(3); // 可以这样想:给我一个可以将提供的值加上 3 的函数
console.log(fnInside(5)); // 8
console.log(outside(3)(5)); // 8
保存变量:上例中
inside
被返回时x
是怎么被保留下来的。一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的inside
没有再被引用时,内存才会被释放。
多层嵌套函数
- 函数(
A
)可以包含函数(B
),后者可以再包含函数(C
)。 - 这里的函数
B
和C
都形成了闭包,所以B
可以访问A
,C
可以访问B
。 - 此外,因为
C
可以访问B
(而B
可以访问A
),所以C
也可以访问A
。
闭包可以包含多个作用域;它们递归地包含了所有包含它的函数作用域。这个称之为作用域链
function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z);
}
C(3);
}
B(2);
}
A(1); // 打印 6(即 1 + 2 + 3)
------------------------------------
//实例2
function outside() {
const x = 5;
function inside(x) {
return x * 2;
}
return inside;
}
console.log(outside()(10)); // 20(而不是 10)
//这里的作用链域是 {inside、outside、全局对象}。因此 inside 的 x 优先于 outside 的 x
---------------------------------------------------------------------------------
const createPet = function (name) {
let sex;
const pet = {
// 在这个上下文中:setName(newName) 等价于 setName: function (newName)
setName(newName) {
name = newName;
},
getName() {
return name;
},
getSex() {
return sex;
},
setSex(newSex) {
if (
typeof newSex === "string" &&
(newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")
) {
sex = newSex;
}
},
};
return pet;
};
const pet = createPet("Vivie");
console.log(pet.getName()); // Vivie
pet.setName("Oliver");
pet.setSex("male");
console.log(pet.getSex()); // male
console.log(pet.getName()); // Oliver
----------------------------------------------------------------------------------------
//内部函数保留"稳定"而又"被封装"的数据参与运行。
const getCode = (function () {
const apiCode = "0]Eal(eh&2"; // 我们不希望外部能够修改的代码......
return function () {
return apiCode;
};
})();
console.log(getCode()); // "0]Eal(eh&2"
arguments对象
函数的实际参数会被保存在一个类似数组的 arguments 对象中
使用 arguments
对象,你可以处理比声明更多的参数来调用函数。这在你事先不知道会需要将多少参数传递给函数时十分有用。你可以用 arguments.length
来获得实际传递给函数的参数的数量,然后用 arguments
对象来访问每个参数。
function myConcat(separator) {
let result = ""; // 初始化列表
// 迭代 arguments
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
console.log(myConcat("、", "红", "橙", "蓝"));
// "红、橙、蓝、"
console.log(myConcat(";", "大象", "长颈鹿", "狮子", "猎豹"));
// "大象;长颈鹿;狮子;猎豹;"
console.log(myConcat("。", "智者", "罗勒", "牛至", "胡椒", "香菜"));
// "智者。罗勒。牛至。胡椒。香菜。"
函数参数
默认参数
function multiply(a, b) {
b = typeof b !== "undefined" ? b : 1;
return a * b;
}
-----------------------------------------
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 5
剩余参数
将不确定数量的参数表示为数组
function multiply(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x);
}
const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
无单独的this
在箭头函数出现之前,每一个新函数都定义了自己的 this 值(在构造函数中是一个新的对象;在严格模式下是 undefined;在作为"对象方法"调用的函数中指向这个对象;等等)。事实证明,这对于面向对象的编程风格来说并不理想。
//箭头函数没有自己的 this,而是使用封闭执行上下文的 this 值。
//因此,在以下代码中,传递到 setInterval 中的函数内的 this 与闭包函数中的 this 相同:
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // 这里的 `this` 正确地指向 person 对象
}, 1000);
}
const p = new Person();
预定义函数
eval()
方法执行方法计算以字符串表示的 JavaScript 代码。
parseFloat()
函数解析字符串参数,并返回一个浮点数。
parseInt()
函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。
表达式与运算符
解构:一个能从数组或对象对应的数组结构或对象字面量里提取数据的 Javascript 表达式。
var foo = ["one", "two", "three"];
// 不使用解构
var one = foo[0];
var two = foo[1];
var three = foo[2];
// 使用解构
var [one, two, three] = foo;
比较运算符
位运算符与位逻辑运算符
数值推导
[for (i of [ 1, 2, 3 ]) i*i ];
// [ 1, 4, 9 ]
var abc = [ "A", "B", "C" ];
[for (letters of abc) letters.toLowerCase()];
// [ "a", "b", "c" ]
this
假设一个用于验证对象value
属性的validate
函数,传参有对象,最高值和最低值。
function validate(obj, lowval, hival) {
if (obj.value < lowval || obj.value > hival) console.log("Invalid Value!");
}
你可以在任何表单元素的onchange
事件处理中调用validat
函数,用this
来指代当前的表单元素,用例如下:
<p>Enter a number between 18 and 99:</p>
<input type="text" name="age" size="3" onChange="validate(this, 18, 99);" />
instanceof
如果所判别的对象确实是所指定的类型,则返回true
var theDay = new Date(1995, 12, 17);
if (theDay instanceof Date) {
// statements to execute
}
in
如果所指定的属性 确实存在于所指定的对象 中,则会返回true
// Arrays
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees; // returns true
3 in trees; // returns true
6 in trees; // returns false
"bay" in trees; // returns false (you must specify the index number,
// not the value at that index)
"length" in trees; // returns true (length is an Array property)
// Predefined objects
"PI" in Math; // returns true
var myString = new String("coral");
"length" in myString; // returns true
// Custom objects
var mycar = { make: "Honda", model: "Accord", year: 1998 };
"make" in mycar; // returns true
"model" in mycar; // returns true
void
可以用 void 运算符指明一个超文本链接。该表达式是有效的,但并不会在当前文档中进行加载。
//下面的代码创建了一个超链接,当用户单击它时,提交一个表单。
<a href="javascript:void(document.form.submit())">Click here to submit</a>
typeof
typeof 操作符返回一个表示 operand 类型的字符串值 。operand 可为字符串、变量、关键词或对象,其类型将被返回。operand 两侧的括号为可选。
var myFun = new Function("5 + 2");
var shape = "round";
var size = 1;
var today = new Date();
--------------------------------------
typeof myFun; // returns "function"
typeof shape; // returns "string"
typeof size; // returns "number"
typeof today; // returns "object"
typeof dontExist; // returns "undefined"
delete
x = 42;
var y = 43;
myobj = new Number();
myobj.h = 4; // create property h
delete x; // returns true (can delete if declared implicitly)
delete y; // returns false (cannot delete if declared with var)
delete Math.PI; // returns false (cannot delete predefined properties)
delete myobj.h; // returns true (can delete user-defined properties)
delete myobj; // returns true (can delete if declared implicitly)
-------------------------------------------------------------------------
逗号操作符
逗号操作符(
,
)对两个操作数进行求值并返回最终操作数的值。它常常用在for
循环中,在每次循环时对多个变量进行更新。
var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var a = [x, x, x, x, x];
for (var i = 0, j = 9; i <= j; i++, j--)
console.log("a[" + i + "][" + j + "]= " + a[i][j]);
短路求值
false
&& anything // 被短路求值为 falsetrue
|| anything // 被短路求值为 true
字符串运算符
var myString = "alpha";
myString += "bet"; // 返回 "alphabet"
前端模块化
前端模块化CommonJS、AMD、CMD、ES6_commonjs amd cmd es6模块化-CSDN博客
ES6:
<script type="module" src="main.js"></script>
//默认导出
export default function(ctx) {
...
}
-----------------------------------
//模块命名
// inside module.js
export { function1 as newFunctionName, function2 as anotherNewFunctionName };
// inside main.js
import { newFunctionName, anotherNewFunctionName } from "/modules/module.js";
-------------------------------------------------------------------------------
//全部引入
import * as Module from "/modules/module.js";
Module.function1();
Module.function2();
-------------------------------------------------------------------------------
//动态加载模块,返回一个promise
squareBtn.addEventListener("click", () => {
import("/js-examples/modules/dynamic-module-imports/modules/square.js").then(
(Module) => {
let square1 = new Module.Square(
myCanvas.ctx,
myCanvas.listId,
50,
50,
100,
"blue",
);
square1.draw();
square1.reportArea();
square1.reportPerimeter();
},
);
});
数字和日期(用时参考数字和日期 - JavaScript | MDN)
字符串对象
const firstString = "2 + 2"; //创建一个字符串字面量
const secondString = new String("2 + 2"); // 创建一个字符串对象
eval(firstString); // 返回数字 4
eval(secondString); // 返回包含 "2 + 2" 的字符串对象
--------------------------------------------------------------------
//String 对象有一个属性 length,标识了字符串中 UTF-16 的码点个数。
//通过数组的方式访问每一个码点,但你不能修改每个字符,因为字符串是不变的类数组对象
const hello = "Hello, World!";
const helloLength = hello.length;
hello[0] = "L"; // 无效,因为字符串是不变的
hello[0]; // 返回 "H"
常见方法
正则表达式
var reg = /正则表达式/修饰符;
修饰符:
- i: ignoreCase, 匹配忽视大小写
- m: multiline , 多行匹配
- g: global , 全局匹配
|------|------------------------------------------------------------|
| [] | var reg = /[abc]/;//匹配abc任意一个字符 var reg1 = /abc/;//匹配abc |
| ^ | var reg1 = /[^abc]/;//匹配abc之外的字符 |
| ^ | var reg = /^abc/;//匹配以abc开头的字符 |
| $ | var reg = /abc$/;//匹配以abc结尾的字符 |
| . | 匹配除换行符以外的任意字符 |
| \w | 匹配字母或数字或下划线或汉字 |
| \s | 匹配任意的空白符 |
| \d | 匹配数字 |
| \b | 匹配单词的开始或结束 |
字符次数匹配
或运算符a (dog|cat) =>a dog/a cat
字符类[a-z] [^a-z]
元字符 \d数字 ^ $
<.+?>
数组
const wisenArray = Array.of(9.3); // wisenArray 只包含一个元素:9.3
const arr = Array(9.3); // RangeError: Invalid array length
-------------------------------------------------------------------
const arr = [];
arr[3.4] = "Oranges";
console.log(arr.length); // 0
console.log(Object.hasOwn(arr, 3.4)); // true
遍历数组
由于 JavaScript 元素被保存为标准对象属性,因此不建议使用 for...in 循环遍历 JavaScript 数组,因为普通元素和所有可枚举属性都将被列出。
//for
const colors = ["red", "green", "blue"];
for (let i = 0; i < colors.length; i++) {
console.log(colors[i]);
}
--------------------------------------------
//foreach
const colors = ["red", "green", "blue"];
colors.forEach((color) => console.log(color));
|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #### 常见方法 ||
| concat() | let myArray = ["1", "2", "3"]; myArray = myArray.concat("a", "b", "c"); // myArray 现在是 ["1", "2", "3", "a", "b", "c"] |
| join() | const myArray = ["Wind", "Rain", "Fire"]; const list = myArray.join(" - "); // list 现在是 "Wind - Rain - Fire" |
| push()最后 unshift()第一 | const myArray = ["1", "2"]; myArray.push("3"); // myArray 现在是 ["1", "2", "3"] const myArray = ["1", "2", "3"]; myArray.unshift("4", "5"); // myArray 变成了 ["4", "5", "1", "2", "3"] |
| pop()最后 shift()第一 | const myArray = ["1", "2", "3"]; const last = myArray.pop(); // myArray 现在是 ["1", "2"],last 为 "3" const myArray = ["1", "2", "3"]; const first = myArray.shift(); // myArray 现在是 ["2", "3"],first 为 "1" |
| slice() | let myArray = ["a", "b", "c", "d", "e"]; myArray = myArray.slice(1, 4); // [ "b", "c", "d"] // 从索引 1 开始,提取所有的元素,直到索引 3 为止 |
| at() | const myArray = ["a", "b", "c", "d", "e"]; myArray.at(-2); // "d",myArray 的倒数第二个元素 |
| reverse() | const myArray = ["1", "2", "3"]; myArray.reverse(); // 将原数组颠倒,myArray = [ "3", "2", "1" ] |
| flat() | let myArray = [1, 2, [3, 4]]; myArray = myArray.flat(); // myArray 现在是 [1, 2, 3, 4],因为子数组 [3, 4] 已被展平 |
| splice() | JavaScript中的splice方法_splice jds-CSDN博客 |
| sort() | const sortFn = (a, b) => { if (a[a.length - 1] < b[b.length - 1]) { return -1; // 负数 => a < b,a 在 b 之前 } else if (a[a.length - 1] > b[b.length - 1]) { return 1; // 正数 => a > b,a 在 b 之后 } return 0; // 0 => a = b,a 和 b 保持原来的顺序 }; myArray.sort(sortFn); // 对数组排序,myArray = ["Wind","Fire","Rain"] > * 如果 a
小于 b
,返回 -1
(或任何负数) > * 如果 a
大于 b
,返回 1
(或任何正数) > * 如果 a
和 b
相等,返回 0
。 |
| indexof()/lastIndexof() | const a = ["a", "b", "a", "b", "a"]; console.log(a.indexOf("b")); // 1 // 再试一次,这次从最后一次匹配之后开始 console.log(a.indexOf("b", 2)); // 3 console.log(a.indexOf("z")); // -1, 因为找不到 'z' |
| foreach() | 方法对数组中的每个元素执行 callback
并返回 undefined
a.forEach((element)=>{}) |
| map() | 方法返回由每个数组元素上执行 callback
的返回值所组成的新数组。 const a2 = a1.map((item) => item.toUpperCase()); |
| flatMap() map()+flat() | const a1 = ["a", "b", "c"]; const a2 = a1.flatMap((item) => [item.toUpperCase(), item.toLowerCase()]); console.log(a2); // ['A', 'a', 'B', 'b', 'C', 'c'] |
| filter() | 方法返回一个新数组 ,其中包含 callback
返回 true
的元素。 const a1 = ["a", 10, "b", 20, "c", 30]; const i = a1.find((item) => typeof item === "number"); console.log(i); // 10 |
| find() findLast() findIndex() findLastIndex() | 方法返回 callback
返回 true
的第一个元素 方法返回 callback
返回 true
的最后一个元素 方法返回 callback
返回 true
的第一个元素的索引 方法返回 callback
返回 true
的最后一个元素的索引 |
| every() some() | * 如果 callback
对数组中的每一个元素都返回 true
,则 every() 方法返回 true
* 如果 callback
对数组中至少一个元素返回 true
,则 some() 方法返回 true
。 |
| reduce() | * reduce()方法对数组中的每个值执行 callback (accumulator, currentValue, currentIndex, Array)
,目的是将列表中的元素减少到单个值 。reduce
函数返回 callback
函数返回的最终值 * 最常用的场景就是,计算数组中的每一项的总和。 * JavaScript中reduce()详解及使用方法。_js reduce-CSDN博客 const a = [10, 20, 30]; const total = a.reduce( (accumulator, currentValue) => accumulator + currentValue, 0, ); console.log(total); // 60 |
- 接受回调的
forEach
方法(以及下面的其他方法)被称为迭代方法 ,因为它们以某种方式遍历整个数组。每个都接受第二个可选的参数thisArg
。如果提供,thisArg
将成为回调函数体中this
关键字的值。如果没有提供,就像在明确的对象上下文之外被调用一样,当函数在严格模式下时,this
是undefined
,当函数在非严格模式下时,this
将引用全局对象(window、globalThis 等。)。
稀疏数组
数组迭代器 属性迭代
//创建
// Array 构造函数:
const a = Array(5); // [ <5 empty items> ]
// 数组字面量中的连续逗号:
const b = [1, 2, , , 5]; // [ 1, 2, <2 empty items>, 5 ]
// 直接给大于 array.length 的索引设置值以形成空槽:
const c = [1, 2];
c[4] = 5; // [ 1, 2, <2 empty items>, 5 ]
// 通过直接设置 .length 拉长一个数组:
const d = [1, 2];
d.length = 5; // [ 1, 2, <3 empty items> ]
// 删除一个元素:
const e = [1, 2, 3, 4, 5];
delete e[2]; // [ 1, 2, <1 empty item>, 4, 5 ]
--------------------------------------------------------------------------
//在某些操作中,空槽的行为就像它们被填入了 undefined 那样
const arr = [1, 2, , , 5]; // 创建一个稀疏数组
// 通过索引访问
console.log(arr[2]); // undefined
// For...of
for (const i of arr) {
console.log(i);
}
// 输出:1 2 undefined undefined 5
// 展开运算
const another = [...arr]; // "another" 为 [ 1, 2, undefined, undefined, 5 ]
------------------------------------------------------------------------------
//在其他方法,特别是数组迭代方法时,空槽是被跳过的
const mapped = arr.map((i) => i + 1); // [ 2, 3, <2 empty items>, 6 ]
arr.forEach((i) => console.log(i)); // 1 2 5
const filtered = arr.filter(() => true); // [ 1, 2, 5 ]
const hasFalsy = arr.some((k) => !k); // false
// 属性迭代
const keys = Object.keys(arr); // [ '0', '1', '4' ]
for (const key in arr) {
console.log(key);
}
// 输出:'0' '1' '4'
// 在对象中使用展开,使用属性枚举,而不是数组的迭代器
const objectSpread = { ...arr }; // { '0': 1, '1': 2, '4': 5 }
类数组对象
一些 JavaScript 对象,如 document.getElementsByTagName() 返回的 NodeList 或 arguments 等 JavaScript 对象,有与数组相似的行为,但它们并不共享数组的所有方法。
arguments
对象提供了 length 属性,但没有实现如 forEach() 等数组方法。
function printArguments() {
arguments.forEach((item) => {
console.log(item);
}); // TypeError: arguments.forEach is not a function
}
--------------------------------------------------------------
function printArguments() {
Array.prototype.forEach.call(arguments, (item) => {
console.log(item);
});
}
使用数组存储其他属性
一个数组作为字符串和正则表达式的匹配结果时,数组将会返回相关匹配信息的属性和元素
const arr = [1, 2, 3];
arr.property = "value";
console.log(arr.property); // "value"
带键的集合
Java中Map|Set......
Map
使用 for...of 循环来在每一次迭代中得到
[key, value]
数组
- set()
- get()
- has()
- delete()
- .size
const sayings = new Map();
sayings.set("dog", "woof");
sayings.set("cat", "meow");
sayings.set("elephant", "toot");
sayings.size; // 3
sayings.get("dog"); // woof
sayings.get("fox"); // undefined
sayings.has("bird"); // false
sayings.delete("dog");
sayings.has("dog"); // false
for (const [key, value] of sayings) {
console.log(`${key} goes ${value}`);
}
// "cat goes meow"
// "elephant goes toot"
sayings.clear();
sayings.size; // 0
一般地,object 会被用于将字符串类型映射到值。
Object
允许设置键值对、根据键获取值、删除键、检测某个键是否存在。不过,Map
对象还有一些优点,可以更好地应用于映射关系表示中。
Object
的键均为字符串或 Symbol 类型,在Map
里键可以是任意类型。- 必须手动计算
Object
的大小,但是可以很容易地获取Map
的大小(size
)。Map
的遍历遵循元素的插入顺序。Object
有原型,所以映射中有一些缺省的键。(可以用map = Object.create(null)
回避)。这三条提示可以帮你决定用
Map
还是Object
:
- 如果键在运行时才能知道,或者所有的键类型相同,所有的值类型相同 ,那就使用
Map
。- 如果需要将原始值存储为键,则使用
Map
,因为Object
将每个键视为字符串,不管它是一个数字值、布尔值还是任何其他原始值。- 如果存在需要对个别元素进行操作的逻辑,使用
Object
。
Set
Set 对象是一组唯一值的集合,可以按照添加顺序来遍历。
Set
中的值只能出现一次;它在集合Set
中是唯一的。
- add()
- has()
- delete()
- .size
const mySet = new Set();
mySet.add(1);
mySet.add("some text");
mySet.add("foo");
mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2
for (const item of mySet) {
console.log(item);
}
// 1
// "some text"
数组与Set之间的转换
可以使用 Array.from 或展开语法来完成集合到数组的转换。同样,
Set
的构造函数 接受数组作为参数,可以完成从Array
到Set
的转换。Array.from(mySet); [...mySet2]; mySet2 = new Set([1, 2, 3, 4]);
一般情况下,在 JavaScript 中使用数组来存储一组元素,而新的
Set
对象有这些优势:
- 根据值(
arr.splice(arr.indexOf(val), 1)
)删除数组元素效率低下。Set
对象允许根据值删除元素,而数组中必须使用基于元素下标的splice
方法。- 数组的
indexOf
方法无法找到 NaN 值。Set
对象存储唯一值,所以不需要手动处理包含重复值的情况。
Map
对象和Set
对象的键和值的等值判断都基于 SameValueZero 算法:Javascript中的相等性判断
- ===------严格相等(三个等号)
三等号(
===
)做的比较与双等号相同(包括对NaN
、-0
和+0
的特殊处理[++当两个都不是NaN
,并且数值相同,或是两个值分别为+0
和-0
时,两个值被认为是相等的++ 。])但不进行类型转换; 如果类型不同,则返回false
- ==------宽松相等(两个等号)
在比较两个操作数时,双等号(
==
)将执行类型转换 ,并且会按照 IEEE 754 标准对NaN
、-0
和+0
进行特殊处理(故NaN != NaN
,且-0 == +0
);
如果操作数具有相同的类型,则按以下方式进行比较:
- Object:仅当两个操作数引用相同 的对象时,才返回
true
。- String:仅当两个操作数具有相同的字符并且顺序相同,才返回
true
。- Number:仅当两个操作数具有相同的值时,才返回
true
。+0
和-0
被视为相同的值。如果任一操作数为NaN
,则返回false
;因此NaN
永远不等于NaN
。- Boolean:仅当操作数都是
true
或false
时,才返回true
。- BigInt:仅当两个操作数具有相同的值时,才返回
true
。- Symbol :仅当两个操作数引用相同的 symbol 时,才返回
true
。如果操作数之一为
null
或undefined
,则另一个操作数必须为null
或undefined
才返回true
。否则返回false
。如果操作数之一是对象,而另一个是原始值,则将对象转换为原始值。
在这一步骤中,两个操作数都被转换为原始值(String、Number、Boolean、Symbol 和 BigInt 之一)。剩余的转换将分情况完成。
- 如果它们是相同类型的,则使用步骤 1 进行比较。
- 如果操作数中有一个是 Symbol,但另一个不是,则返回
false
。- 如果操作数之一是 Boolean,而另一个不是,则将 Boolean 转换为 Number:
true
转换为 1,false
转换为 0。然后再次对两个操作数进行宽松比较。- Number 转 String:将 String 转换为 Number。转换失败会得到
NaN
,这将确保相等性为false
。- Number 转 BigInt:按照其数值进行比较。如果 Number 是
±Infinity
或NaN
,返回false
。- String 转 BigInt: 使用与 BigInt() 构造函数相同的算法将字符串转换为 BigInt。如果转换失败,则返回
false
。const num = 0;
const big = 0n;
const str = "0";
const obj = new String("0");console.log(num == str); // true
console.log(big == num); // true
console.log(str == big); // trueconsole.log(num == obj); // true
console.log(big == obj); // true
console.log(str == obj); // true**Object.is()**既不进行类型转换,也不对
NaN
、-0
和+0
进行特殊处理 (这使它和===
在除了那些特殊数字值之外的情况具有相同的表现)// 向 Nmuber 构造函数添加一个不可变的属性 NEGATIVE_ZERO Object.defineProperty(Number, "NEGATIVE_ZERO", { value: -0, writable: false, configurable: false, enumerable: false, }); function attemptMutation(v) { Object.defineProperty(Number, "NEGATIVE_ZERO", { value: v }); }
对象
枚举一个对象的所有属性
for...in 循环 该方法依次访问一个对象及其原型链中所有可枚举的属性。
Object.keys(o) 该方法返回对象
o
自身包含(不包括原型中 )的所有可枚举属性的名称的数组。Object.getOwnPropertyNames(o) 该方法返回对象
o
自身包含(不包括原型中 )的所有属性 (无论是否可枚举) 的名称的数组。ES5之前法枚举一个对象的所有属性
function listAllProperties(o) {
var objectToInspect;
var result = [];for ( objectToInspect = o; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect) ) { result = result.concat(Object.getOwnPropertyNames(objectToInspect)); } return result;
}
Object.create()
它允许你为创建的对象选择一个原型对象,而不用定义构造函数。
// Animal properties and method encapsulation var Animal = { type: "Invertebrates", // 属性默认值 displayType: function () { // 用于显示 type 属性的方法 console.log(this.type); }, }; // 创建一种新的动物------animal1 var animal1 = Object.create(Animal); animal1.displayType(); // Output:Invertebrates // 创建一种新的动物------Fishes var fish = Object.create(Animal); fish.type = "Fishes"; fish.displayType(); // Output:Fishes
Object.defineProperty()设置getter、setter
var d = Date.prototype;
Object.defineProperty(d, "year", {
get: function () {
return this.getFullYear();
},
set: function (y) {
this.setFullYear(y);
},
});
------------------------------------------
var o = { a: 0 };
Object.defineProperties(o, {
b: {
get: function () {
return this.a + 1;
},
},
c: {
set: function (x) {
this.a = x / 2;
},
},
});
o.c = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
console.log(o.b); // Runs the getter, which yields a + 1 or 6
删除属性(一个不是继承而来)
//Creates a new object, myobj, with two properties, a and b.
var myobj = new Object();
myobj.a = 5;
myobj.b = 12;
//Removes the a property, leaving myobj with only the b property.
delete myobj.a;
----------------------------------------------------------------------
g = 17;
delete g;
比较对象
在 JavaScript 中 objects 是一种引用类型 。两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回 true.
// 两个变量,两个具有同样的属性、但不相同的对象 var fruit = { name: "apple" }; var fruitbear = { name: "apple" }; fruit == fruitbear; // return false fruit === fruitbear; // return false ---------------------------------------------- // 两个变量,同一个对象 var fruit = { name: "apple" }; var fruitbear = fruit; // 将 fruit 的对象引用 (reference) 赋值给 fruitbear // 也称为将 fruitbear"指向"fruit 对象 // fruit 与 fruitbear 都指向同样的对象 fruit == fruitbear; // return true fruit === fruitbear; // return true
类 JVM基础二------类的生命周期-CSDN博客
声明一个类
//ES6之前
function MyClass() {
this.myField = "foo";
// 构造函数体
}
MyClass.myStaticField = "bar";
MyClass.myStaticMethod = function () {
// myStaticMethod 体
};
MyClass.prototype.myMethod = function () {
// myMethod 体
};
(function () {
// 静态初始化代码
})();
--------------------------------------------------
//ES6
class MyClass {
// 构造函数
constructor() {
// 构造函数体
}
// 实例字段
myField = "foo";
// 实例方法
myMethod() {
// myMethod 体
}
// 静态字段
static myStaticField = "bar";
// 静态方法
static myStaticMethod() {
// myStaticMethod 体
}
// 静态块
static {
// 静态初始化代码
}
// 字段、方法、静态字段、静态方法、静态块都可以使用私有形式
#myPrivateField = "bar";
}
私有属性
在 Chrome 控制台中运行的代码可以在类外访问私有字段,JavaScript 为了方便调试而仅在 DevTools 中放宽了这一限制。
class Color {
// 声明:每个 Color 实例都有一个名为 #values 的私有字段。
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
getRed() {
return this.#values[0];
}
setRed(value) {
this.#values[0] = value;
}
}
const red = new Color(255, 0, 0);
console.log(red.getRed()); // 255
-----------------------------------------------------------
console.log(red.#values); // SyntaxError: Private field '#values' must be declared in an enclosing class
应该隐藏实例的内部数据结构,以保持 API 的简洁性,并防止在你做了一些"无害的重构"时,用户代码不至于崩溃。在类中,这是通过私有字段来实现的。
私有字段是以 **
#
(井号)**开头的标识符。class Color { // 声明:每个 Color 实例都有一个名为 #values 的私有字段。 #values; constructor(r, g, b) { this.#values = [r, g, b]; } getRed() { return this.#values[0]; } setRed(value) { this.#values[0] = value; } } const red = new Color(255, 0, 0); console.log(red.getRed()); // 255
类方法可以读取其他实例的私有字段,只要它们属于同一个类即可。
class Color { #values; constructor(r, g, b) { this.#values = [r, g, b]; } redDifference(anotherColor) { // #values 不一定要从 this 访问: // 你也可以访问属于同一个类的其他实例的私有字段。 return this.#values[0] - anotherColor.#values[0]; } } const red = new Color(255, 0, 0); const crimson = new Color(220, 20, 60); red.redDifference(crimson); // 35
otherColor
并非一个Color
实例,#values
将不存在(即使另一个类有一个同名的私有字段,它也不是同一个东西,也不能在这里访问)。访问一个不存在的私有字段会抛出错误,而不是像普通属性一样返回undefined
。如果你不知道一个对象上是否存在一个私有字段,且你希望在不使用try
/catch
来处理错误的情况下访问它,你可以使用 in 运算符。class Color { #values; constructor(r, g, b) { this.#values = [r, g, b]; } redDifference(anotherColor) { if (!(#values in anotherColor)) { throw new TypeError("Color instance expected"); } return this.#values[0] - anotherColor.#values[0]; } }
类https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_classes
静态属性与方法
class MyClass { static { MyClass.myStaticProperty = "foo"; } } console.log(MyClass.myStaticProperty); // 'foo' ---------------------------------------------------- class Color { static isValid(r, g, b) { return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255; } } Color.isValid(255, 0, 0); // true Color.isValid(1000, 0, 0); // false
在访问
this
之前,必须调用 super()class ColorWithAlpha extends Color { // ... static isValid(r, g, b, a) { // 调用父类的 isValid(),并在此基础上增强返回值 return super.isValid(r, g, b) && a >= 0 && a <= 1; } } console.log(ColorWithAlpha.isValid(255, 0, 0, -1)); // false
扩展与继承静态方法
class ColorWithAlpha extends Color { #alpha; constructor(r, g, b, a) { super(r, g, b); this.#alpha = a; } get alpha() { return this.#alpha; } set alpha(value) { if (value < 0 || value > 1) { throw new RangeError("Alpha 值必须在 0 与 1 之间"); } this.#alpha = value; } }
Promise
doSomething()
.then((url) => {
// fetch(url) 前缺少 `return` 关键字。
fetch(url);
})
.then((result) => {
// result 是 undefined,因为上一个处理器没有返回任何东西。
// 无法得知 fetch() 的返回值,也无法知道它是否成功。
});
代理
对象可以拦截某些操作并实现自定义行为
let handler = {
get(target, name) {
return name in target ? target[name] : 42;
},
};
let p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
-----------------------------------------------------
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return `[[${name}]]`;
},
},
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", `typeof` 不会触发任何陷阱
反射
- Reflect.has(Object, "assign"); // true 判断是否有属性、方法
- 调用一个具有给定
this
值和arguments
数组(或****类数组对象****)的函数。在不借助
Reflect
的情况下,我们通常使用 Function.prototype.apply() 方法调用一个具有给定this
值和arguments
数组(或类数组对象)的函数。Function.prototype.apply.call(Math.floor, undefined, [1.75]); Reflect.apply(Math.floor, undefined, [1.75]); // 1; Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]); // "hello" Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index; // 4 Reflect.apply("".charAt, "ponies", [3]); // "i"
检查属性定义是否成功
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
迭代器与生成器
迭代器
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能附带一个返回值。
更具体地说,迭代器是通过使用
next()
方法实现了迭代器协议的任何一个对象,该方法返回具有两个属性的对象:function makeRangeIterator(start = 0, end = Infinity, step = 1) { let nextIndex = start; let iterationCount = 0; const rangeIterator = { next() { let result; if (nextIndex < end) { result = { value: nextIndex, done: false }; nextIndex += step; iterationCount++; return result; } return { value: iterationCount, done: true }; }, }; return rangeIterator; } let it = makeRangeIterator(1, 10, 2); let result = it.next(); while (!result.done) { console.log(result.value); // 1 3 5 7 9 result = it.next(); } console.log(`已迭代序列的大小:${result.value}`); // 5
生成器
最初调用时,生成器函数不执行任何代码,而是返回一种称为生成器 的特殊迭代器。通过调用
next()
方法消耗该生成器时,生成器函数将执行,直至遇到yield
关键字。function* makeRangeIterator(start = 0, end = Infinity, step = 1) { let iterationCount = 0; for (let i = start; i < end; i += step) { iterationCount++; yield i; } return iterationCount; }
可迭代对象
//自定义可迭代对象
var myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
[...myIterable]; // [1, 2, 3]
内置可迭代对象
String、Array、TypedArray、Map 和 Set 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法。
for (let value of ["a", "b", "c"]) { console.log(value); } // "a" // "b" // "c" [..."abc"]; // ["a", "b", "c"] function* gen() { yield* ["a", "b", "c"]; } gen().next(); // { value: "a", done: false } [a, b, c] = new Set(["a", "b", "c"]); a; // "a"
斐波那契数列生成器
function* fibonacci() { let current = 0; let next = 1; while (true) { const reset = yield current; [current, next] = [next, next + current]; if (reset) { current = 0; next = 1; } } } const sequence = fibonacci(); console.log(sequence.next().value); // 0 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 2 console.log(sequence.next().value); // 3 console.log(sequence.next().value); // 5 console.log(sequence.next().value); // 8 console.log(sequence.next(true).value); // 0 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 2