声明
- const:声明一个常量,let:声明一个变量;const/let 声明的常量/变量都只能作用于代码块(块级作用域或函数作用域)里;
if (true) {
let name = "布兰";
}
console.log(name); // undefined
- const/let 不允许在同一个作用域内,重复声明;
function setName(name) {
let name = ""; // SyntaxError
}
- const 声明时必须初始化,且后期不能被修改,但如果初始化的是一个对象,那么不能修改的是该对象的内存地址;
const person = {
name: "bbb",
};
person.name = "aaa";
console.log(person.name); // 'aaa'
person = ""; // TypeError
- const/let 在全局作用域中声明的常量/变量不会挂到顶层对象(浏览器中是 window )的属性中;
var name = "bbb";
let age = 12;
console.log(window.name); // 'bbb'
console.log(window.age); // undefined
解构赋值
解构类型
- 字符串解构
let [a, b, c = "c"] = "12";
console.log(a, b, c); // '1' '2' 'c'
- 数组解构:等号右侧的数据具有 Iterator 接口可以进行数组形式的解构赋值;
什么样的数据具有 Iterator 接口呢?如果一个对象能够通过 [Symbol.iterator] 访问,且能够返回一个符合迭代器协议的对象,那么该对象就是可迭代的。目前内置的可迭代对象有:String、Array、TypeArray、Map、Set、arguments 和 NodeList 等。
// 解构不成功的变量值为 undefined
let [a, b, c] = [1, 2];
console.log(a, b, c); // 1, 2, undefined
// 可以设置默认值
let [x, y, z = 3] = [1, 2, null];
console.log(x, y, z); // 1, 2, null
- 对象解构:与数组按照索引位置进行解构不同,对象解构是按照属性名进行解构赋值,如果在当前对象属性匹配不成功则会去对象的原型属性上查找:
// 默认写法
let { name: name, age: age } = { name: "ab", age: 12 };
// 简写
let { name, age } = { name: "ab", age: 12 };
// 改名且设置默认值
let { name: name1, age: age1 = 12 } = { name: "ab" };
console.log(name1, age1); // 'ab' 12
- 函数参数解构:其实就是运用上面的对象解构和数组解构规则;
function move({ x = 0, y = 0 } = {}) {
console.log([x, y]);
return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
解构要点
- 只要等号两边的模式相同(同是对象或同是数组),则左边的变量会被赋予对应的值;
- 解构不成功的变量值为 undefined;
- 默认值生效的前提是当等号右边对应的值全等于 undefined 的时候;
- 只要等号右边的值不是对象或者数组,则会进行自动装箱将其转成对象;
- null 和 undefined 都无法转成对象,所以无法解构。
解构应用
- 交换变量的值
let x = 1,
y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
- 通过函数返回对象属性
function getParams() {
return {
name: "布兰",
age: 12,
};
}
let { name, age } = getParams();
- 通过定义函数参数来声明变量
let person = {
name: "布兰",
age: 12,
};
init(person);
// 普通用法
function init(person) {
let { name, age } = person;
}
// 更简洁用法
function init({ name, age }) {}
- 指定函数参数默认值
function initPerson({ name = "布兰", age = 12 } = {}) {
console.log(name, age);
}
initPerson(); // '布兰' 12
initPerson({ age: 20 }); // '布兰' 20
- 提取 JSON 数据
let responseData = {
code: 1000,
data: {},
message: "success",
};
let { code, data = {} } = responseData;
- 遍历 Map 结构
let map = new Map();
map.set("beijing", "北京");
map.set("xiamen", "厦门");
for (let [key, value] of map) {
console.log(key, value);
}
- 输入模块的指定方法和属性
const { readFile, writeFile } = require("fs")
字符串扩展
- 可以使用 Unicode 编码来表示一个字符:
// 以下写法都可以用来表示字符 z
"\z"; // 转义
"\172"; // 十进制表示法
"\x7A"; // 十六进制表示法
"\u007A"; // Unicode 普通表示法
"\u{7A}"; // Unicode 大括号表示法
52unicode 这个网站可以查询到常见符号的 Unicode 编码。
- 可以使用 for...of 正确遍历字符串:
let str = "😀🤣😜😍🤗🤔";
for (const emoji of str) {
console.log(emoji); // 😀🤣😜😍🤗🤔
}
for (let i = 0, l = str.length; i < l; i++) {
console.log(str[i]); // 不能正确输出表情
}
- 模板字符串使用两个反引号标识(``),可以用来定义多行字符串,或者使用它在字符串中插入变量:
let name = "hero";
let tips = `Hello ${name},welcome to my world.`;
alert(tips);
- 标签模板:在函数名后面接一个模板字符串相当于给函数传入了参数进行调用:
let name = "布兰",
age = 12;
let tips = parse`Hello ${name}, are you ${age} years old this year?`;
function parse(stringArr, ...variable) {}
// 相当于传递如下参数进行调用 parse 函数
parse(["Hello ", ", are you ", " years old this year?"], name, age);
-
字符串是否包含子串:
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = "Hello world!";
s.includes("o"); // true
s.startsWith("Hello"); // true
s.endsWith("!"); // true
- 这三个方法都支持第二个参数,表示开始搜索的位置:
let s = "Hello world!";
s.includes("Hello", 6); // false
s.startsWith("world", 6); // true
s.endsWith("Hello", 5); // true
上面代码表示,使用第二个参数 n 时,endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。
- repeat(n) 将当前字符串重复 n 次后,返回一个新字符串:
"x".repeat(2); // 'xx'
"x".repeat(1.9); // 'x'
"x".repeat(NaN); // ''
"x".repeat(undefined); // ''
"x".repeat("2a"); // ''
"x".repeat(-0.6); // '',解释:0 ~ 1 之间的小数相当于 0
"x".repeat(-2); // RangeError
"x".repeat(Infinity); // RangeError
数组扩展
- 数组扩展运算符(...)将数组展开成用逗号分隔的参数序列,只能展开一层数组:
// 应用一:函数传参
Math.max(...[1, 2, 3]); // 3
// 应用二:数组合并
let merge = [...[1, 2], ...[3, 4], 5, 6]; // 1, 2, 3, 4, 5, 6
// 应用三:浅克隆
let a = [1, 2, 3];
let clone = [...a];
a === clone; // false
// 应用四:数组解构
const [x, ...y] = [1, 2, 3];
x; // 1
y; // [2, 3]
- Array.from() 可以将类数组对象( NodeList,arguments)和可迭代对象转成数组:
// 应用一:字符串转数
Array.from("foo"); // ['f', 'o', 'o']
// 应用二:数组合并去重
let merge = [...[1, 2], ...[2, 3]];
Array.from(new Set(merge)); // ['1', '2', '3']
// 应用三:arguments 转数组
function f() {
return Array.from(arguments);
}
f(1, 2, 3); // [1, 2, 3]
如果 Array.from() 带第二个参数 mapFn,将对生成的新数组执行一次 map 操作:
Array.from([1, 2, 3], (x) => x * x); // [1, 4, 9]
Array.from({ length: 3 }, (v, i) => ++i); // [1, 2, 3]
- Array.copyWithin() 在当前数组内部,将制定位置的成员复制到其他位置(会覆盖原来位置的成员),最后返回一个新数组。接收 3 个参数,参数为负数表示右边开始计算:
target(必选):替换位置的索引; start(可选):从该位置开始读取数据,默认为 0; end(可选):从该位置结束读取数据(不包括该位置的数据),默认为原数组长度;
[1, 2, 3, 4, 5]
.copyWithin(-1) // [1, 2, 3, 4, 1]
[(1, 2, 3, 4, 5)].copyWithin(1) // [1, 1, 2, 3, 4]
[(1, 2, 3, 4, 5)].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
[(1, 2, 3, 4, 5)].copyWithin(0, -3, -1); // [3, 4, 3, 4, 5]
- 查找第一个出现的子成员:find() 和 findIndex():
// 找出第一个偶数
[1, 6, 9].find((val, index, arr) => val % 2 === 0); // 6
// 找出第一个偶数的索引位置
[1, 6, 9].findIndex((val, index, arr) => val % 2 === 0); // 1
- fill() 使用给定的值来填充数组,有 3 个参数:
value:填充值; start(可选),开始索引,默认为 0; end(可选):结束索引,默认为数组长度,不包括该索引位置的值;
// 初始化空数组
Array(3).fill(1); // [1, 1, 1]
[1, 2, 3, 4].fill("a", 2, 4); // [1, 2, 'a', 'a']
- 通过 keys()(键名)、entries()(键值)和 values()(键值对) 获取数组迭代器对象,可以被 for...of 迭代,
let arr = ["a", "b", "c"];
for (let x of arr.keys()) {
console.log(x); // 1, 2, 3
}
for (let v of arr.values()) {
console.log(v); // 'a' 'b' 'c'
}
for (let e of arr.entries()) {
console.log(e);
// [0, 'a'] [0, 'b'] [0, 'c']
}
对象扩展
- 对象属性简写
let name = "abc";
let person = {
name,
getName() {
return this.name;
},
};
// 等同于
let person1 = {
name: "abc",
getName: function () {
return this.name;
},
};
- 属性名表达式:在用对象字面量定义对象的时候,允许通过属性名表达式来定义对象属性
let name = 'name',
let person = {
[name]: '布兰',
['get'+ name](){
return this.name
}
}
-
方法的 name 属性,存在好几种情况,这里仅列出常见的几种:
-
情况一:普通对象方法的 name 属性直接返回方法名,函数声明亦是如此,函数表达式返回变量名:
let person = { hi() {}, }; person.hi.name; // 'hi' -
情况二:构造函数的 name 为 anonymous:
(new Function).name // 'anonymous' -
情况三:绑定函数的 name 将会在函数名前加上 bound:
function foo() {} foo.bind({}).name; // 'bound foo' -
情况四:如果对象的方法使用了取值函数(getter)和存值函数(setter),则 name 属性不是在该方法上面,而是该方法的属性的描述对象的 get 和 set 属性上面:
let o = { get foo() {}, set foo(x) {}, }; o.foo.name; // TypeError: Cannot read property 'name' of undefined let descriptor = Object.getOwnPropertyDescriptor(o, "foo"); descriptor.get.name; // "get foo" descriptor.set.name; // "set foo" -
属性的可枚举性
性都有一个描述对象(Descriptor),用来控制该属性的行为。可以通过 Object.getOwnPropertyDescriptor() 来获取对象某个属性的描述:
let person = { name: "布兰", age: 12 }; Object.getOwnPropertyDescriptor(person, "name"); // { // configurable: true, // enumerable: true, // value: "布兰", // writable: true, // }这里的 enumerable 就是对象某个属性的可枚举属性,如果某个属性的 enumerable 值为 false 则表示该属性不能被枚举,所以该属性会被如下 4 种操作忽略:
- for...in :只遍历对象自身的和继承的可枚举的属性;
- Object.keys():返回对象自身的所有可枚举的属性的键名;
- JSON.stringify():只串行化对象自身的可枚举的属性;
- Object.assign(): 只拷贝对象自身的可枚举的属性。
-
-
Object.is() 用来判断两个值是否相等,表现基本和 === 一样,除了以下两种情况:
+0 === -0; //true
NaN === NaN; // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
函数扩展
- 函数参数默认值。
function printInfo(name = "布兰", age = 12) {}
- 使用参数默认值的时候,参数不能有同名的:
function f(x, x, y) {} // 不报错
function f(x, x, y = 1) {} // 报错
- 函数体内不能用 let 和 const 声明同参数名的变量:
// 报错
function f(x, y) {
let x = 0;
}
- 函数的 length 属性会返回没有指定默认值的参数个数,且如果设置默认值的参数不是尾参数,则 length 不再计入后面的参数:
(function f(x, y) {}.length); // 2
(function f(x, y = 1) {}.length); // 1
(function f(x = 1, y) {}.length); // 0
- 剩余(rest) 参数(...变量名)的形式,用于获取函数的剩余参数,注意 rest 参数必须放在最后一个位置,可以很好的代替 arguments 对象:
function f(x, ...y) {
console.log(x); // 1
for (let val of y) {
console.log(val); // 2 3
}
}
f(1, 2, 3);
- 箭头函数语法比函数表达式更简洁,并且没有自己的 this、arguments,不能用作构造函数和用作生成器。几种箭头函数写法:
let f1 = () => {}; // 没有参数
let f2 = (x) => {}; // 1个参数
let f3 = (x) => {}; // 1个参数可以省略圆括号
let f4 = (x, y) => {}; // 2个参数以上必须加上圆括号
let f5 = (x = 1, y = 2) => {}; // 支持参数默认值
let f6 = (x, ...y) => {}; // 支持 rest 参数
let f7 = ({ x = 1, y = 2 } = {}) => {}; // 支持参数解构
箭头函数没有自己的 this;