es6函数相关
自执行函数
- 一个回自己调用自己的函数
- 当这个函数定义好后,直接被调用
语法:
=> (function(){代码})()
=》~(function(){代码})()
=》!(function(){代码})()
// 自调用函数
(function () {
console.log("hello world");
})();
~(function(){
console.log("你好世界");
})()
!(function(){
console.log("你好世界");
})()
函数默认值
书写:直接在书写函数的时候,以赋值符号 给形参设置默认值就可以了
任何函数都行
// 以前的做法
// function add(a,b){
// a = a || 10;
// b = b || 20;
// return a + b;
// }
// let res = add(1,2);
// console.log(res);
// 允许你在()内设置默认值
// function add(a = 10,b = 20){
// return a + b;
// }
// let res = add(100,200);
// console.log(res); // 300
// 箭头函数设置默认值时,一定要带上小括号
let fn = (a=10) =>{
console.log(a);
}
fn()
箭头函数
是 es6 语法中定义函数的一种新方式
只能用来定义函数表达式
当你把函数当作一个值赋值给另一个内容的时候,叫做函数表达式
// 之前的书写方式
// 声明式函数
function f001() {
// 函数体
}
// 表达式函数
let foo2 = function () {
// 函数体
};
// 箭头函数完整写法
let foo3 = (name, age) => {
// 函数体
console.log(name, age);
};
foo3("zd", 88);
let arr = ["aaa", "bbb", "ccc"];
arr.forEach(function (item, index, arr) {
console.log(item, index, arr);
});
arr.forEach((item, index, arr) => {
console.log(item, index, arr);
});
setTimeout(() => {
console.log("abc");
}, 1000);
注意:声明式函数不可以
语法:()=>{}
- ()写形参的位置
- =>是箭头函数的标志
- {}是书写代码块的位置
箭头函数的特点
- 可以省略小括号不写
- 当形参只有一个的时候,可以不写小括号
- 如果你的形参没有或者两个及两个以上,必须写
let fn1 = a => {
console.log(a);
};
fn1(10);
let fn2 = () => {
console.log("hello");
};
fn2();
let fn3 = (a, b, c, d) => {
console.log(a, b, c, d);
};
fn3(1, 2, 3, 4);
- 可以省略大括号不写
当你的代码只有一句话的时候,可以省略大括号,并且会自动返回这一句话的结果,否则,必须书写大括号
let fn4 = (a, b) => console.log(a, b);
let res = fn4(10, 20);
console.log(res); // undefined
let fn5 = (a, b) => a + b;
let res2 = fn4(10, 20);
console.log(res); // 30
let fn6 = (a, b) => a + b;
// 清除偶数
let arr2 = [1, 2, 3, 4, 5, 6, 7];
let res3 = arr2.filter(item => item % 2);
console.log(res3);
- 箭头函数没有 arguments
let fn1 = function () {
console.log(arguments);
};
fn1(10, 20, 30, 40, 50);
let fn2 = () => {
console.log(arguments); // 报错 未定义
};
fn2(10, 20, 30, 40, 50);
箭头函数内 this 的指向
箭头函数中 this 到底指向哪?
不适用那四条规则,而是根据外层作用域来决定
官方:外部作用域的 this
私人:书写在箭头函数外面的那个函数的 this 是谁,箭头函数的 this 就是谁。
console.log(this); // window
let obj = {
f: function () {
console.log("f的this", this);
},
foo() {
// foo函数作用域
// this指向obj
// 箭头函数继承
return () => {
// this指向obj
console.log("f2的this", this);
};
},
f2: () => {
console.log("f2的this", this);
},
};
obj.f(); // obj
obj.f2(); // window
obj.foo()();
// this永远指向函数 运行时 所在的对象(作用域),而不是函数被创建时所在的对象
// js作用域
// 全局作用域
// 局部作用域(函数作用域)
let obj2 = {
name: "obj2",
foo() {
return () => {
console.log(this); // obj2
return function () {
console.log(this); // window
return () => {
console.log(this); // window
};
};
};
},
};
obj2.foo()()()();
/*
{
this obj2
返回一个fn{
箭头函数拿上一层作用域this
this = obj2
返回一个fn{
独立调用
this = window
返回一个fn{
箭头函数
继承上一层的this
window
this = window
}
}
}
}
*/
面试题
// 1.
var name = "window";
let person = {
name: "person",
sayName: function () {
console.log(this.name);
},
};
function sayName() {
let sss = person.sayName;
sss(); // 默认绑定 window window
person.sayName(); // person 隐式绑定 person
person.sayName(); // person 隐式绑定 person
(b = person.sayName)(); // window 默认 window
// 函数间接引用
// 引用了 person对象中sayName
// person对象中sayName赋值给了b
// b()
}
sayName();
// 2.
var name = "window";
let person1 = {
name: "person1",
foo1: function () {
console.log(this.name);
},
foo2: () => {
console.log(this.name);
},
foo3: function () {
return function () {
console.log(this.name);
};
},
foo4: function () {
return () => {
console.log(this.name);
};
},
};
let person2 = { name: "person2" };
//题目开始
person1.foo1(); // 隐式绑定 person1
person1.foo1.call(person2); // 显示绑定 person2
person1.foo2(); // 默认 外层作用域 window
person1.foo2.call(person2); // 外层 window 箭头函数 没有this call无效
person1.foo3()(); // 默认绑定 window
person1.foo3.call(person2)(); // person2 显示绑定
// window
// 改this为person2 改的是 f003
// foo3 return 出来的函数 没改
// 默认绑定 window
person1.foo3().call(person2); // 显示绑定 person2
// 这里改的就是foo3函数return出来的函数指向
// 指向了person2
person1.foo4()(); // 默认绑定 window 默认绑定 person1
// 把foo4指向person1
// return ()=>{} 拿到foo4的this
// person1 person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
// foo4内的this -》person1
// 箭头函数call没用 找外层 person1
// 3.
var name = "window";
function Person(name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
};
};
this.foo4 = function () {
return () => {
console.log(this.name);
};
};
}
let person1 = new Person("person1");
let person2 = new Person("person2");
person1.foo1(); // 隐式绑定 person1
person1.foo1.call(person2); // 显示绑定 person2
person1.foo2(); // person1 上层作用域
person1.foo2.call(person2); // person1 上层作用域
person1.foo3()(); //默认绑定 window
person1.foo3.call(person2)(); // 默认绑定 window
person1.foo3().call(person2); // person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
// 4.
var name = "window";
function Person(name) {
this.name = name;
this.obj = {
name: "obj",
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
},
};
}
let person1 = new Person("person1");
let person2 = new Person("person2");
person1.obj.foo1()(); // window
person1.obj.foo1.call(person2)(); // window
person1.obj.foo1().call(person2); // person2
person1.obj.foo2()(); // person1 obj
person1.obj.foo2.call(person2)(); // person2
person1.obj.foo2().call(person2); // obj
es6 相关
定义变量
es6 新增了两个定义变量的关键字
- let 变量
- const 常量
var/let/const 的区别
- 变量提升
- var:会进行预解析,可以先使用后定义。
- let/const 不会进行预解析,必须先定义后使用。
console.log(a);
var a = 10;
console.log(a);
// console.log(b); // 报错
let b = 20;
console.log(b);
console.log(c); // 报错
const c = 30;
- 变量重名
var 定义的变量可以重名,只是第二个没有意义
let/ const 不允许同一作用域下定义重名变量
// var a = 10;
// var a = 20;
// console.log(a); // 20
// let/ const 不允许同一作用域下重名
let a = 10; // 报错
// let a = 20;
// const a = 30; // 报错
function foo() {
let a = 20;
}
function fn() {
const a = 30;
}
- 块级作用域
- var 没有
- let/const 有
- 块级作用域:任何一个可以书写代码的{}都会限制变量的使用范围
let n = 100;
if (1) {
// let n = 200;
n = 200;
console.log(n); // 200
}
for (let i = 0; i < 5; i++) {
let n = 300;
console.log(n); // 300
}
console.log(n); // 200
let/const 区别
- 声明时的赋值
- let 可以不赋值
- const 必须赋值,否则报错
let a;
console.log(a); // undefined
a = 200;
console.log(a); // 200
// const b;// 报错 没有初始化
- 值的修改
- let 定义的变量可以修改值的内容
- const 不行
let n = 100;
n = 200;
console.log(n);
const m = 10;
// m = 20;
// console.log(m); // 报错 你尝试把一个变量赋值给常量
// obj是复杂数据类型 / 存的是地址
// 当你修改复杂数据类型的值时,obj地址未改变
const obj = {
a: 1,
};
obj.a = 20;
obj.b = 30;
console.log(obj.a);
console.log(obj.b);
// 这样才是在改变obj的值
obj = [];
模版字符串
(``)里面可以识别变量
通过${}
在其中就可以识别变量
语法:正常写入字符串${变量名}正常写入字符串
;
块级作用域
在 ES6 之前,JS 中存在两种作用域
- 全局作用域(全局执行上下文)
- 函数作用域(函数执行上下文)
在 ES6 引入了 let const 从而拥有了块级作用域
为什么要引入块级作用域?(或者说没有块级作用域有什么影响?)
会导致函数中的变量无论在哪里声明的,在编译阶段都会被提取到执行上下文的变量环境中,所以这些变量在整个函数体内部的任何地方都能被访问。这也就是 js 中的变量提升
变量提升带来的问题
- 变量容易在不被察觉的情况下被覆盖
var a = "zd";
function fn() {
console.log(a);
if (0) {
var a = "qt";
}
console.log(a);
}
fn();
// undefined
/*
接着执行第一行代码
console.log(a);
执行这段代码需要使用到变量a,结合刚才的调用栈状态图,可以看到这里有两个a变量,一个在全局执行上下文中,值为yh,另一个在fn函数的执行上下文中值为undefined,
根据作用域的访问机制
访问当前作用域中的变量a
得到undefined
*/
- 本应销毁的变量没有被销毁
function foo() {
for (var i = 0; i < 7; i++) {}
console.log(i);
}
foo();
// 同样是由于变量提升导致,在创建执行上下文阶段,变量i就已经被提升了,所以当for循环结束后,变量i并没有被销毁
js 怎么支持的块级作用域的
- 一步编译并创建执行上下文
- 通过上图已知
- 函数内部通过 var 声明的变量,在编译阶段全都被保存放到变量环境里面了
- 通过 let 声明的变量,在编译阶段会被存放到词法环境中
- 在函数内部的块作用域,通过 let 声明的变量并没有被存放到词法环境中
- 执行代码
-
变量环境中的 a 的值被赋值为 1,词法环境中 b 的值被赋值为 2.
-
当进入函数的块作用域中时,会在词法环境中生成一个新的区域,这个区域中的变量并不影响块作用域外面的变量。
作用域外面声明的变量 b,在该作用域内部也声明了变量 b,当执行到作用域内部时,他们都是独立的存在
在词法环境中,相当于维护了一个小型的栈结构,栈底是函数最外层的变量,进入一个块作用域后,就会把该作用域内部的变量压到栈顶;当作用域执行完成后,该作用域信息就会从栈顶弹出。这就是词法环境的结构。
-
当执行到 console.log(a) console.log(b)时
需要在词法环境中和变量环境中查找变量 a 的值。
查找方法:
沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 js 引擎,如果没有查到,那么就继续在变量环境中查找。
-
当块作用域执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出。
解构赋值
快速从对象/数组中获取你要的数据
数组
- 使用
[]
解构数组 - 语法:var/let/const [变量 1,变量 2,…] = 数组
- 按照索引,依次从数组中每一个变量赋值
const arr = ["金拱门", "德克士", "华莱士", "kfc", "翰博king"];
// 原来的做法
// let a = arr[0];
// console.log(a);
// let 变量 = 数组[索引下标]
// es6语法
const [a, b, c, d, e] = arr;
console.log(a, b, c, d, e); // 金拱门 德克士 华莱士 kfc 翰博king
console.log(a); // 金拱门
const [f, g] = arr;
console.log(f, g); // 金拱门 德克士
// 多维数组
const arr = [1, 2, 3, [4, 5, [6, [7, 8, 9]]]];
// let a = arr[3][2][1][2];
// console.log(a);
// const [a,b,c,[d,e,[f,[g,h,i]]]] = arr;
// console.log(i);
// let a = 10;
// let b = 20;
// [a,b] = [b,a]
// console.log(a);
// console.log(b);
// 使用逗号跳过索引
const [, , , [, , [, [, , a]]]] = arr;
console.log(a);
对象
- 语法:var/let/const {键名 1,键名 2} = 对象
- 注意:
{}
解构对象
解构时起别名
- 语法:var/let/const {键名 1:别名,键名 2:别名} = 对象
- 注意:当你起了别名之后,原先的键名就不能再当作变量名了;来使用了,需要用这个别名
const obj = { name: "zd", age: 88, gender: "男" };
// let age = 18;
// 以前
// let name = "name";
// console.log(window);
// let userName = obj["name"];// obj["name"]
// console.log(userName);
// const {name,age,gender} = obj;
// console.log(name,age,gender);// zd 88 男
// 解构的时候起个别名
const { name: a, age: b, gender: c } = obj;
// let a = obj.name
// let b = obj.age
// let c = obj.gender
console.log(a, b, c);
console.log(name); // 打印的是window下的name属性
// 当你起了别名之后,原先的键名就不能再当作变量名了;来使用了,需要用这个别名
console.log(age); //报错
扩展运算符...
es6 的扩展运算符,又叫做展开合并运算符
有两个意义:展开,合并
主要是操作 数组和对象的运算符号
私人:打散,聚拢
打散
打散对象/数组
如果是打散数组,就是去掉数组的[]
如果是打散对象,就是去掉对象的{}
- 数组
const arr = [1, 2, 3, 4];
console.log(arr[0], arr[1], arr[2], arr[3]);
console.log(...arr);
const arr2 = [...arr, 5, 6, 7];
// [1,2,3,4,5,6,7]
console.log(arr2);
const res = Math.max(...arr2);
console.log(res);
let lis = [...document.querySelectorAll("li")];
let res1 = lis.filter(item => item.innerText % 2);
console.log(res1);
- 对象
const o1 = {name:"老夹子",age:88};
// 允许的
const o2 = {
gender:"男",
...o1,
}
console.log(o2);
// console.log(...o1); // 报错
function fn(){}
// fn(...o1) // 报错
// 允许的
console.log({...o1});
聚拢
当这个符号书写在函数的形参位置的时候,叫合并运算符
从当前形参的位置开始获取实参,一直到末尾
注意:合并运算符一定要写在最后一位形参位置上
const fn = function(a,b,...c){
console.log(arguments);
console.log(a,b,c);
}
fn(1,2,3,4,5,6,7,8,9)
// 给箭头函数一个实参数组
const fn2 = (...arg)=>{
console.log(arg);
console.log(arg.filter(item=>item > 3));
}
fn2(1,2,3,4,5,6,7,8)
for…of循环
是ES6新引入的一种遍历所有数据结构的统一方式。
所有数据结构:部署了
[Symbol.iterator]
属性的数据结构
使用
// 遍历数组
let arr = [1, 2, 3, 4, 5];
for (let item of arr) {
console.log(item); // 数组的值
// 如果想要在forof中拿到数组的索引
// indexOf
// 设置一个变量从0开始记录
// 可以通过 entries 可以获取到数组中每一项的键名与键值 每一项以一个数组形式返回,这个数组的0项是当前的索引(键名),1项是键值
// 键名:数组的下标索引
// 键值:数组当前想的值
// console.log(Object.entries(arr));
// keys 返回是一个数组,数组里面装载的是当前对象的键名
console.log(Object.keys(arr));
// values 返回是一个数组,数组里面装载的是当前对象的键值
console.log(Object.values(arr));
}
// 遍历对象
let obj = {
name: "zd",
age: 8,
};
// for(let k of obj){
// console.log(k); // 报错,obj不可迭代
// }
let objKeys = Object.keys(obj);
console.log(objKeys);
for(let key of objKeys){
console.log(obj[key]);
}
let objValues = Object.values(obj);
console.log(objValues);
function foo(a,b) {
console.log(a);
console.log(b);
}
foo(...objValues)
for of与for in的区别
- for in只是获取数组的索引,for of会获取数组的值
- for in会遍历对象的整个原型链,性能差;而for of 只遍历当前对象,不会遍历原型链
- 对于数组的遍历,for in会返回数组中所有可枚举属性(包含原型链上的可枚举属性);for of
- for in循环主要是为了遍历对象而设计的,不适用于遍历数组
- for of使用遍历数组/字符串/map/set 等有迭代器对象的集合但是不能遍历普通对象
新增数据类型
Symbol
- ES6引入了一种新的基本数据类型 Symbol,表示独一无二的值。
- Symbol 值 通过Symbol()函数生成。
这就是说,对象的属性名现在可以有两种类型,一种是原来的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol 类型,就都是独一无二的,可以保证不会与其他的属性名产生冲突。
let s = Symbol();
console.log(s);
let s2 = Symbol();
console.log(s == s2);
console.log(typeof s); // symbol
// 注意:Symbol函数前面不能使用 new 命令,否则会报错
let s3 = new Symbol(); // Uncaught TypeError: Symbol is not a constructor
这是因为生成的Symbol是一个基本数据类型,不是对象,所以不能用new名来调用。另外,由于Symbol值不是对象,所以也不能添加属性。基本上他是一种类似于字符串的数据类型。
- Symbol()函数可以接受一个字符串作为参数,表示对Symbol实例的描述。主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s4 = Symbol("zd");
console.log(s4);
let s5 = Symbol("qt");
console.log(s4 === s5);
let s6 = Symbol("zd");
console.log(s4 === s6);
// Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数返回的值是不想等的。
console.log(s5.toString());
console.log(s6.toString());
- 注意:Symbol值可以转成字符串和布尔值,但是不能转为数值
console.log(s5.toString());
console.log(s6.toString());
let s7 = Symbol("jh");
console.log(Boolean(s7));
console.log(!s7);
console.log(Number(s7)); // 报错
读区Symbol的描述 Symbol.description
正常情况下,我们想要读区Symbol的描述需要讲Symbol转为字符串
let s1 = Symbol("rb");
console.log(String(s1));// Symbol(rb)
console.log(s1.toString());// Symbol(rb)
// 在 ES2019中提供了一个Symbol值的实例属性 description,可以直接返回 Symbol的描述
console.log(s1.description); // rb
作为属性名的Symbol
- 由于Symbol唯一的特性,将Symbol用于对象的属性名,就能保证不会出现同名属性 。
let mySymbol = Symbol();
// 第一种写法
let obj = {};
obj[mySymbol] = "Hello";
console.log(obj);
console.log(obj[mySymbol]);
// 第二种写法
let obj2 = {
[mySymbol]:"Hello",
}
console.log(obj2);
console.log(obj2[mySymbol]);
// 第三种写法
let obj3 = {
[Symbol("name")]:"yh",
[Symbol("age")]:18,
gender:"man"
}
// 注意:Symbol值作为对象属性名时,不能使用点运算符
let obj4 = {};
obj4.mySymbol = "Hello";
// 因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值
// 导致obj4的属性名实际上是一个字符串,而不是一个Symbol值
console.log(obj4[mySymbol]);
console.log(obj4["mySymbol"]);
// 同理,在对象内部,使用Symbol值定义属性时,Symbol值必须放在方括号中。
let obj5 = {
mySymbol:"Hello",
}
console.log(obj5);
其他应用
// 其他应用
// 将Symbol作为值的一大好处在于,在做某些条件判断的时候,不会因为出现相同的值而导致程序出错,保证程序按照我们的设计的方式工作。
const color_red = Symbol("red");
const color_green = Symbol("green");
function Color(color){
switch (color){
case color_red:
console.log("red");
break;
case color_green:
console.log("green");
break;
}
}
let res = color_green;
Color(res)
遍历带有Symbol作为键名的对象
- Symbol作为键名时,该属性不会出现在for in循环中。
获取指定对象中所有的Symbol属性名
- 语法:Object.getOwnPropertySymbols(你要获取的对象)
- 返回值:返回一个数组,里面装载的就是当前这个对象中所有作为属性名的Symbol值
let obj = {
[Symbol("name")]:"zd",
[Symbol("age")]:28,
[Symbol("gender")]:"男",
height:150
}
console.log(obj);
let objSymbols = Object.getOwnPropertySymbols(obj);
console.log(objSymbols);
console.log(obj[objSymbols[0]]);
- 语法:Reflect.ownKeys(要获取的对象)
- 返回值:返回一个数组,里面装载的是这个对象中所有数据的键名(所有类型的键名,包含常规键名和Symbol键名)
let objKey = Reflect.ownKeys(obj);
console.log(objKey); // ['height', Symbol(name), Symbol(age), Symbol(gender)]
Symbol.for()
有时我们想要重新使用同一个Symbol值,但Symbol的特性让每一次生成一个全新的Symbol值。
- 作用:用于创建多个相同的Symbol值
- 语法:Symbol.for(“名称”)
let a = Symbol("foo");
let b = Symbol.for("foo")
let c = Symbol.for("foo")
console.log(a === b); // false
console.log(b === c); // true
原理:通过for创建的Symbol值,他接受一个字符串作为参数,之后会被登记在全局环境中供搜索,每次通过for创建Symbol值时,他首先会在全局环境中根据你提供的名称(key)搜索,如果检查出给定的key不存在才会创建一个新的值,否则将返回检查到的 Symbol 值。
let a = Symbol("foo");
let b = Symbol.for("foo")
let c = Symbol.for("foo")
// let c = b;
console.log(a === b); // false
console.log(b === c); // true
let e = Symbol.for()
let f = Symbol.for()
let g = Symbol.for("")
console.log(e===f); // true
console.log(f===g); // false
let i = Symbol.for();
console.log(i === e);// true
let j = Symbol.for("")
console.log(j === g); // true
let k = Symbol.for(null);
console.log(k === e);// false
Symbol.keyFor();
- 返回一个已登记的Symbol类型值的key(名称
) - 语法:Symbol.keyFor(Symbol值)
// 变量s1属于未登记,所以返回undefined
let s1 = Symbol("foo");
console.log(Symbol.keyFor(s1)); // undefined
let s2 = Symbol.for("foo");
console.log(Symbol.keyFor(s2)); // foo