4-1. 参数默认值
使用
在书写形参时,直接给形参赋值,附的值即为默认值
这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是undefined),则会自动使用默认值。
[扩展]对arguments的影响
只要给函数加上参数默认值,该函数会自动变量严格模式下的规则:arguments和形参脱离
[扩展]留意暂时性死区
形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区。
例子
例子1
function test(a = b, b) {
console.log(a, b);
}
test(undefined, 2);
例子2
function sum(a, b = 1, c = 2) {
return a + b + c;
}
console.log(sum(10, undefined, undefined))
console.log(sum(11))
console.log(sum(1, undefined, 5))
例子3
function getContainer() {
console.log("abc");
return document.getElementById("container");
}
/**
* 创建一个元素
* @param {*} name 元素的名称
* @param {*} container 元素的父元素
* @param {*} content 元素的内容
*/
function createElement(name = "div", container = getContainer(), content = "") {
const ele = document.createElement(name)
if (content) {
ele.innerHTML = content;
}
container.appendChild(ele);
}
createElement(undefined, undefined, "手动阀手动阀十分")
createElement(undefined, undefined, "234242342424")
createElement(undefined, document.getElementById("container"), "234242342424")
例子4
function test(a, b = 1) {
console.log("arugments", arguments[0], arguments[1]);
console.log("a:", a, "b:", b);
a = 3;
console.log("arugments", arguments[0], arguments[1]);
console.log("a:", a, "b:", b);
}
test(1, 2);
4-2. 剩余参数
arguments的缺陷:
- 如果和形参配合使用,容易导致混乱
- 从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图
ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
语法:
function (...形参名){
}
一个具体的例子
/**
* 对所有数字求和
* @param {...any} args
*/
function sum(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
/**
* 获取一个指定长度的随机数组成的数组
* @param {*} length
*/
function getRandomNumbers(length) {
const arr = [];
for (let i = 0; i < length; i++) {
arr.push(Math.random());
}
return arr;
}
const numbers = getRandomNumbers(10);
//将数组的每一项展开,依次作为参数传递,而不是把整个数组作为一个参数传递
// sum(numbers)
console.log(sum(...numbers))//相当于传递了10个参数
console.log(sum(1, 3, ...numbers, 3, 5))
从上面可以看到一个场景就是我们的参数其实都放在一个数组里面,我们在调用函数的时候又希望按照每一个参数分别传给函数,此时函数的剩余参数就十分有用了。
细节:
- 一个函数,仅能出现一个剩余参数
- 一个函数,如果有剩余参数,剩余参数必须是最后一个参数
例子
例子1
function test(a, b, ...args) {
}
test(1, 32, 46, 7, 34);
例子2
function sum(...args) {
//args收集了所有的参数,形成的一个数组
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
console.log(sum())
console.log(sum(1))
console.log(sum(1, 2))
console.log(sum(1, 2, 3))
console.log(sum(1, 2, 3, 4))
例子3
下面的写法报错,因为args1 args2 的个数没法确定。
function test(...args1, ...args2) {
console.log(args1)
console.log(args2)
}
test(1, 32, 46, 7, 34);
4-3. 展开运算符
使用方式: ...要展开的东西
对数组展开 ES6
对对象展开 ES7
例子1 浅克隆
const obj1 = {
name: "成哥",
age: 18,
loves: ["邓嫂", "成嫂1", "成嫂2"],
address: {
country: "中国",
province: "黑龙江",
city: "哈尔滨"
}
}
// 浅克隆到obj2
const obj2 = {
...obj1,
name: "邓哥",
address: {
...obj1.address
},
loves: [...obj1.loves, "成嫂3"]
};
console.log(obj2)
console.log(obj1.loves === obj2.loves)
这个展开默认是浅克隆。注意以下几点: 1.如果obj2我们想给某个属性重新赋值,我们只需要像 name: "邓哥"这样重新赋值就可以了。 2.由于对对象是浅克隆,这里的obj2 的loves数组 和obj1 的loves数组,克隆完是同一个对象,如果想对这个对象进行克隆,我们可以新建 [] 数组,在这个数组里面对该属性进行展开,并且可以对数组增加一些项。
例子2 数字求和
/**
* 对所有数字求和
* @param {...any} args
*/
function sum(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
克隆数组的元素
const arr1 = [3, 67, 8, 5];
//克隆arr1数组到arr2
const arr2 = [0, ...arr1, 1];
console.log(arr2, arr1 === arr2)
MAX也可以传入多个参数
btn.onclick = function () {
const numbers = getValues(); //得到文本框中的所有数字形成的数组
spanmax.innerText = Math.max(...numbers)
spanmin.innerText = Math.min(...numbers)
}
函数柯里化需要研究(暂时不懂)
<script>
function cal(a, b, c, d) {
return a + b * c - d;
}
//curry:柯里化,用户固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接收剩余的参数
function curry(func, ...args) {
return function(...subArgs) {
const allArgs = [...args, ...subArgs];
if (allArgs.length >= func.length) {
//参数够了
return func(...allArgs);
} else {
//参数不够,继续固定
return curry(func, ...allArgs);
}
}
}
const newCal = curry(cal, 1, 2)
console.log(newCal(3, 4)) // 1+2*3-4
console.log(newCal(4, 5)) // 1+2*4-5
console.log(newCal(5, 6)) // 1+2*5-6
console.log(newCal(6, 7)) // 1+2*6-7
const newCal2 = newCal(8)
console.log(newCal2(9)); // 1+2*8-9
</script>
4-5. 明确函数的双重用途
ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用
new.target
//该表达式,得到的是:如果没有使用new来调用函数,则返回undefined
//如果使用new调用函数,则得到的是new关键字后面的函数本身
例子
function Person(firstName, lastName) {
//判断是否是使用new的方式来调用的函数
// //过去的判断方式
// if (!(this instanceof Person)) {
// throw new Error("该函数没有使用new来调用")
// }
if (new.target === undefined) {
throw new Error("该函数没有使用new来调用")
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
const p1 = new Person("袁", "进");
console.log(p1)
4-6. 箭头函数
回顾:this指向
- 通过对象调用函数,this指向对象
- 直接调用函数,this指向全局对象
- 如果通过new调用函数,this指向新创建的对象
- 如果通过apply、call、bind调用函数,this指向指定的数据
- 如果是DOM事件函数,this指向事件源
使用语法
箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数
完整语法:
(参数1, 参数2, ...)=>{
//函数体
}
如果参数只有一个,可以省略小括号
参数 => {
}
如果箭头函数只有一条返回语句,可以省略大括号,和return关键字,是一条语句,但不是返回语句也不行。
参数 => 返回值
简写返回一个对象的写法:直接返回一个对象的时候要加一个小括号,告诉编辑器这是一个表达式。
const sum = (a, b) => ({
a: a,
b: b,
sum: a + b
});
console.log(sum(3, 5))
vue 里面的一个箭头函数
new Vue({
// render(h) {
// return h(App);
// },
render: (h) => h(App), // 渲染组件App
}).$mount("#app");
上下两个render函数,可以从内部从这个配置对象调用方式去理解,调用方式是一致的。
注意细节
- 箭头函数中,不存在this、arguments、new.target,如果使用了,则使用的是函数外层的对应的this、arguments、new.target
- 箭头函数没有原型
- 箭头函数不能作用构造函数使用
应用场景
- 临时性使用的函数,并不会可以调用它,比如:
- 事件处理函数
- 异步处理函数
- 其他临时性的函数
- 为了绑定外层this的函数
- 在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
不会使用箭头函数的场景
对象的属性是不会用箭头函数的。 为什么不会用呢? 因为他会导致一个问题。箭头函数内部的this指向外层。
const obj = {
count: 0,
start: function () {
//this
setInterval(() => {
//此处this指向箭头函数声明的地方,相当于上面一行的this,对象函数里面的this,如果是下面的 obj.start的方式调用,this指向window.
this.count++;
console.log(this.count);
}, 1000)
},
start: function () {
setInterval(function() {
//此处 this指向window
this.count++;
console.log(this.count);
}, 1000)
},
regEvent: function () {
//this
window.onclick = () => {
//此处this指向箭头函数声明的地方,相当于上面一行的this,对象函数里面的this,如果是下面的 obj.start的方式调用,this指向window.
console.log(this.count);
}
},
regEvent1: function () {
window.onclick = function() {
//此处this指向事件源 window
console.log(this.count);
}
},
print: function () {
console.log(this)
console.log(this.count)
}
//不会像下面使用箭头函数
print:()=> {
// obj.print();调用的话,this指向window。
console.log(this)
console.log(this.count)
}
//因为上面可以简化为
print: this, this不在函数里面,指向 window。这样会导致想用当前对象的一些东西,不能用this.属性名去调用了。
}
// obj.start();
// obj.regEvent();
obj.print();
this指向哪里呢
const func = () => {
console.log(this)
}
const obj = {
method: func
}
//这里this不指向 obj,而是指向window。
obj.method();
箭头函数里面的this取决于箭头函数声明的位置的this。
在有箭头的函数里面判断this的指向是该箭头函数的声明替换成this,然后看一下这是this指向哪里,函数里面的this就指向哪里,如果里面仍然是箭头函数,继续依次往外看。
const obj = {
// 指向window
this,
// 下面的this可能指向window,可能指向obj
print: function () {
console.log(this)
}