目录
1、匿名函数
2、立即执行
3、默认参数
4、arguments
5、箭头函数
6、递归调用
7、回调函数
8、标签函数
9、this
(1)构造函数
(2)对象字面量
(3)箭头函数
10、apply / call / bind
(1)原理分析
(2)apply / call
(3)bind
1、匿名函数
匿名函数没有函数名,它也不会做任何事情。通常将匿名函数和事件处理程序一起使用。
函数是对象所以可以通过赋值来指向到函数对象的指针,当然指针也可以传递给其他变量。
function() {
console.log('hello');
};
let div = document.querySelector('div');
div.addEventListener('click', function () {
div.style.background = 'pink';
});
下面代码使用函数表达式将匿名函数赋值给变量,这个变量也可以赋值给其他变量。
let f = function (i) {
setInterval(() => {
console.log(i++);
}, 1000);
};
f(0);
let f1 = f;
f1(0);
匿名函数也成为函数表达式,与函数声明有一定的区别,函数声明会进行函数提升,但函数表达式不会。
console.log(fun1(0));
function fun1(i) {
setInterval(() => {
console.log(i++);
}, 1000);
}
console.log(fun2(0));
// Uncaught ReferenceError: Cannot access 'fun2' before initialization
let fun2 = function (i) {
setInterval(() => {
console.log(i++);
}, 1000);
}
2、立即执行
指函数定义时立即执行。
作用:可以用来定义私有作用域防止污染全局作用域。
(function () {
var name = '张三';
console.log(name); // 张三
})();
console.log(name); //
3、默认参数
ES6 支持函数定义默认参数:
function f(name = '张三', age = 18) {
return `name: ${name}, age: ${age}`;
}
console.log(f()); // name: 张三, age: 18
console.log(f('李四', 20)); // name: 李四, age: 20
如果不是全部参数都有默认值,则要将没有默认值的参数放在有默认值的参数的前面:
function f(name, age = 18) {
return `name: ${name}, age: ${age}`;
}
console.log(f()); // name: undefined, age: 18
console.log(f('张三')); // name: 张三, age: 18
function f1(name = '张三', age) {
return `name: ${name}, age: ${age}`;
}
console.log(f1()); // name: 张三, age: undefined
console.log(f1(18)); // name: 18, age: undefined
console.log(f1('张三' ,18)); // name: 张三, age: 18
4、arguments
arguments 是获取到函数的所有参数的集合:
function sum() {
return [...arguments].reduce((total, num) => {
return (total += num);
} ,0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 当然展开语法也能达到同样的效果,所以更推荐展开语法
function Sum(...args) {
return args.reduce((num1, num2) => num1 + num2);
}
console.log(sum(1, 2, 3, 4)); // 10
5、箭头函数
ES6 新增使用胖箭头 ( => ) 语法定义函数表达式的能力。任何可以使用函数表达式的地方,都可以使用箭头函数。
let sum = (a, b) => {
return a + b;
}
console.log(sum(1, 2)); // 3
// 当函数体为单一表达式时,可以不需要 return ,系统会自动返回表达式计算的值。
let sum = (a, b) => a + b;
console.log(sum(1, 2)); // 3
箭头函数简洁的语法很适合嵌入式函数的场景:
let num = [1, 2, 3, 4];
console.log(num.map(x => { return x + 1; })); // [2, 3, 4, 5]
箭头函数虽然简洁,但是也有很多场景不适用:①不能使用 arguments 、super 和 new.target ;②不能用作构造函数;③没有 prototype 属性。
6、递归调用
在函数内部调用自身的方式叫做递归。
需要注意:
- 主要用于循环次数不确定的情况
- 必须要有程序出口,否则会陷入死循环
// 阶乘
function f(num = 1) {
return num == 1 ? num : num * f(--num);
}
console.log(f(5)); // 120
// 累加
function sum(...args) {
return args.length == 0 ? 0 : args.pop() + sum(...args);
}
console.log(sum(1, 2, 4, 5)); // 12
7、回调函数
通过函数参数传递到其它代码的,某一块可执行代码的引用。
例如鼠标单击事件:
let div = document.querySelector('div');
div.addEventListener('click', function () {
alter('点击了div');
});
8、标签函数
标签函数的语法是函数名后面直接带一个模板字符串,并从模板字符串中的插值表达式中获取参数。
let name = '张三';
let age = 18;
function f(str, name, age) {
console.log(str);
console.log(name);
console.log(age);
}
f`I'm ${name}, I'm ${age} year old.`;
// ["I'm ", ", I'm ", " year old.", raw: Array(3)]
// 张三
// 18
f(["I'm ", ", I'm ", " year old."], name, age);
// ["I'm ", ", I'm ", " year old."]
// 张三
// 18
let str = `I'm ${name}, I'm ${age} year old.`;
f(str);
// I'm 张三, I'm 18 year old.
// undefined
// undefined
上面的代码定义了一个函数,三个参数分别为传入的str、name、age。通过分别①使用标签函数和模板字符串、②正常传参使用,可以看到,当作为标签函数使用时,函数的第一个参数为以模板字符串中变量为分隔符分割出的字符串数组、后续所有参数分别对应每一个变量。
若将模板字符串赋值给一个变量,然后将变量传入函数,则函数的第一个参数为正常的字符串,后续所有参数都为 undefined 。
9、this
在标准函数中,this 引用的是把函数当成方法调用的上下文对象(在全局上下文中调用函数时,this 执行 windows )。
(1)构造函数
当函数被 new 时即为构造函数,一般构造函数中包含属性和方法。
function sayColor() {
this.color = 'red';
this.say = function () {
console.log(this); // sayColor {color: "red", say: ƒ}
return this.color;
}
}
let sc = new sayColor();
console.log(sc.say()); // red
(2)对象字面量
window.color = 'blue';
let obj = {
color: 'red',
sayColor() {
console.log(this.color); // red
console.log(this); // {color: "red", sayColor: ƒ}
function showColor() {
console.log(this.color); // blue
console.log(this); // Window {window: Window, self: Window, document: document, name: "", location: Location, …}
}
showColor();
}
};
obj.sayColor();
上面的代码中,sayColor() 函数在 obj 中,属于对象方法,指向 obj 。
而 showColor() 函数不在 obj 中,在 window 中,不属于对象方法,指向 window 。
可以在父作用域将 this 赋值给变量,如 that 。来改变其指向:
window.color = 'blue';
let obj = {
color: 'red',
sayColor() {
console.log(this.color); // red
console.log(this); // {color: "red", sayColor: ƒ}
const that = this;
function showColor() {
console.log(that.color); // red
console.log(that); // {color: "red", sayColor: ƒ}
}
showColor();
}
};
obj.sayColor();
(3)箭头函数
箭头函数没有 this ,可以理解为箭头函数中的 this 会继承定义箭头函数时的上下文,即和外层函数指向同一个 this 。
window.color = 'blue';
let obj = {
color: 'red'
};
let sayColor = () => console.log(this.color);
sayColor(); // blue
obj.sayColor = sayColor;
obj.sayColor(); // blue
上面的代码中,两次对 sayColor 的调用结果都是 blue ,因为这个箭头函数在定义的时候 this 指向的就是 window ,两次调用中 this 指向未发生改变。
function sayRed() {
this.color = 'red';
setTimeout(() => {
console.log(this.color);
}, 1000);
}
new sayRed(); // red
function sayBlue() {
this.color = 'blue';
setTimeout(function() {
console.log(this.color);
}, 1000);
}
new sayBlue(); // undefined
// function sayBlue() {
// this.color = 'blue';
// setTimeout(() => {
// console.log(this.color);
// }, 1000);
// }
// new sayBlue(); // blue
在事件回调或定时回调中调用某个函数时,this 值指向的并非是想要的对象。此时将回调函数写成箭头函数就可以解决问题。因为箭头函数中的 this 会保留定义时的上下文。
function sayRed() {
this.color = 'red';
setTimeout(() => {
console.log(this.color);
console.log(this);
// Window {window: Window, self: Window, document: document, name: "", location: Location, …}
} ,1000);
}
sayRed(); // blue
function sayBlue() {
this.color = 'blue';
setTimeout(() => {
console.log(this.color);
console.log(this);
// Window {window: Window, self: Window, document: document, name: "", location: Location, …}
} ,1000);
}
sayBlue(); // blue
console.log(window.color); // blue
可以看出,上面代码中两个函数的输出结果都是 blue ,因为 setTimeout 中的延迟执行函数的 this 指向永远为 window ,所以在执行时,因为 sayBlue 中将 window.color 改为了 blue ,所以导致 sayRed 中输出也为 blue 。
- 使用普通函数时,this 指向当前 DOM 对象:
<body>
<button desc="李四">button</button>
<script>
let Dom = {
name: '张三',
bind() {
const button = document.querySelector('button');
button.addEventListener('click', function() {
console.log(this.getAttribute('desc'));
});
}
};
Dom.bind(); // 李四
</script>
</body>
- 使用箭头函数时 this 指向上下文对象:
<body>
<button desc="李四">button</button>
<script>
let Dom = {
name: '张三',
bind() {
const button = document.querySelector('button');
button.addEventListener('click', event => {
console.log(this.name + event.target.innerHTML);
});
}
};
Dom.bind(); // 张三button
</script>
</body>
- 使用 handleEvent 绑定事件处理器时,this 指向当前对象而不是 DOM 元素:
<body>
<button desc="李四">button</button>
<script>
let Dom = {
name: '张三',
handleEvent: function (event) {
console.log(this);
},
bind() {
const button = document.querySelector('button');
button.addEventListener('click', this);
}
};
Dom.bind(); // {name: "张三", handleEvent: ƒ, bind: ƒ}
</script>
</body>
10、apply / call / bind
改变 this 指针,也可以理解为对象借用方法,就现像生活中向朋友借东西一样。
(1)原理分析
构造函数中的 this 是一个空对象,经过构造函数处理后将其变为有值。
function f(name) {
this.name = name;
console.log(name);
}
f('张三'); // 张三
call() 可以改变构造函数中的空对象,即让构造函数中的 this 指向另一个对象。
function f(name) {
this.name = name;
}
let obj = {};
f.call(obj, '李四');
console.log(obj.name); // 李四
(2)apply / call
apply 与 call 用于显示的设置函数的上下文,两个方法作用一样都是将对象绑定到this,只是在传递参数上有所不同。
- apply 用数组传参,call 需要分别传参;
- apply 和 call 会立刻执行函数,但是 bind 不会。
function show(title) {
console.log(`${title+this.name}`);
}
let person1 = {
name: '张三'
};
let person2 = {
name: '李四'
};
show.call(person1, 'name'); // name张三
show.apply(person2, ['name']); // name李四
(3)bind
将函数绑定到某个对象,如 b = a.bind() 为将 a 绑定到 b 上,即 b.a() 。
- bind 是复制函数行为,会返回新的函数;
- apply 和 call 会立刻执行函数,但是 bind 不会。
function a() { }
let b = a;
console.log(a === b); // true
let c = a.bind();
console.log(a === c); // false
console.log(a == c); // false
绑定参数注意事项:
function func(a, b) {
return this.f + a + b;
}
// 使用 bind 会生成新函数
let newFunc = func.bind({ f: 1 }, 3); // bind 中的两个参数分别给到 this.f 和 a
// 参数 2 赋值给 b ,即 a = 3 ,b = 2 (1+3+2)
console.log(newFunc(2)); // 6