函数的定义和调用
1 函数的定义方式
1.1 function 关键字 (命名函数)
function fn(){}
1.2 函数表达式(匿名函数)
var fn = function(){}
- 命名函数实际上会声明一个变量(函数名),然后把函数对象赋值给它;函数表达式不会声明变量,最佳实践是使用
const把函数表达式赋值给常量,以防止给它赋予新值而重写函数。 - 命名函数先创建函数对象,然后运行包含代码赋值给变量(函数名),函数的定义会被提升到顶部;函数表达式在表达式实际被求值以前无法调用函数。
1.3 new Function()
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
typeof f // function
var fn = new Function('参数1','参数2'..., '函数体')
注意:
- Function 里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是 Function 的实例
- 函数也属于对象
1.4 箭头函数
():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const sum = (x, y) => { return x + y }
箭头函数与普通函数的区别:
- 箭头函数中不绑定this,箭头函数中的this继承至是它所定义的环境;普通函数的this指向执行上下文。
- 进而,箭头函数没有原型,不能做构造函数。(原因在于通过new关键字实例化对象时,无法改变this的指向) 箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题
2 函数的调用
/* 1. 普通函数 */
function fn() {
console.log('人生的巅峰');
}
fn();
/* 2. 对象的方法 */
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
/* 6. 立即执行函数(自调用函数)*/
(function() {
console.log('人生的巅峰');
})();
/* 7. 通过call, apply间接调用 */
Math.max.call([], 1,2,3) // 多用于继承
Math.max.apply([], [1,2,3]) // 多与数组联系
函数实参与形参
javascript函数定义不会指定函数形参的类型,函数调用也不对传入的实参进行类型检查,也不检查实参的个数。
1. 可选形参与默认值
当调用函数时传入的实参少于定义的形参时,额外的形参会获得默认值undefined。
function getPropertyNames(o, a) {
if (a === undefined) a = []
for (let property in o) a.push(property)
return a
}
var o = { x: 1 }
getPropertyNames(o) // ['x']
ES6之后,可以在函数形参列表中为每个参数定义默认值。
function getPropertyNames(o, a=[]) {
for (let property in o) a.push(property)
return a
}
var o = { x: 1 }
getPropertyNames(o) // ['x']
2. 剩余形参与可变长度实参列表
剩余形参能够编写在调用时传入比形参多任意数量的实参的函数。
// 返回最大值
function max(first=-Infinity, ...rest) {
let maxValue = first
for (let val of rest) {
if (val > maxValue) {
maxValue = val
}
}
return maxValue
}
max(1,10,100,2,3) // 100
剩余形参rest是ES6语法,在ES6之前,通过Arguments对象实现变长函数。
function max(first=-Infinity) {
let maxValue = first
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] > maxValue) {
maxValue = arguments[i]
}
}
return maxValue
}
max(1,10,100,2,3) // 100
3. 函数中扩展运算符
var numbers = [1,10,100,2,3]
function max(f) {
return function(...args) {
return f(...args)
}
}
var f = max(Math.max)
f(1,2,3) // 3
在函数定义中使用...可以将多个函数实参收集到一个数组中;在函数调用中使用...将可迭代对象展开。
4. 剩余参数和解构
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
闭包
闭包(
closure)指有权访问另一个函数作用域中变量的函数。简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。(最简单的例子,函数里面return函数,return的函数使用了外层函数的变量)
每个闭包都是引用自己的词法作用域的变量,一个闭包内对变量的修改,不会影响到另一个闭包中的变量。
闭包的作用
延伸变量的作用范围(同时也是它的缺陷之一——内存泄漏)。 闭包真正值得关注的时候,是定义函数与调用函数的作用域不同的时候。
var makeCount = function() {
var privateCount = 0
function changeCount(val) {
privateCount += val
}
return {
increment:function() {
changeCount(1)
},
getCount: function() {
return privateCount
}
}
}
// count1 与 count2 并不共用privateCount
var count1 = makeCount()
var count2 = makeCount()
console.log(count1.getCount()) // 0
count1.increment()
console.log(count1.getCount()) // 1
console.log(count2.getCount()) // 0
count2.increment()
count2.increment()
console.log(count2.getCount()) // 2
函数的属性,方法和构造函数
函数也是一种特殊的对象,因此,也有属性和方法
| 属性 | 含义 |
|---|---|
| length | 只读,参数列表中声明的形参个数 |
| name | 函数名,变量名 |
| prototype | 引用一个被称为原型对象的对象 |
方法:call(), apply(), bind(),后续函数中的this指向详细介绍。此外还有,toString(), Function()构造函数。
函数式编程
1. 使用函数处理数组
常见的数组函数reduce(), map()就是经典的函数式编程思想。
const sum = (x, y) => x + y
let data = [1,2,3,4,5]
let mean = reduce(data, sum) / data.length // 求平均值
let deviations = map(data, x => x-mean) // 偏差
2. 高阶函数
高阶函数就是
操作函数的函数, 接收一个或多个函数作为参数并返回一个新函数。
function not(f) {
return function(...args) {
let result = f(...args)
return !result
}
}
const even = x => x % 2 === 0 // 判断数值为偶数
const odd = not(even) // 判断数值为奇数
console.log([1,1,3,5].every(odd))
3. 函数记忆(memoization)
高阶函数接收一个函数参数,返回这个函数的记忆版(类似于动态规划算法)
function memoize(f) {
const cache = new Map()
return function(...args) {
// 缓存键
let key = args.length + args.join("+")
if (cache.has(key)) return cache.get(key)
else {
let result = f.apply(this, args)
cache.set(key, result)
console.log(cache); // 为 4,3,2,1缓存了值
return result
}
}
}
// 求阶乘
const factorial = memoize(function(n) {
return (n <= 1) ? 1 : n * factorial(n-1)
})
factorial(5) // 120
函数内this的指向
函数内this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同,一般指向调用者。箭头函数不会改变this指向
改变函数内部 this 指向
1. call方法
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向
应用场景: 经常做继承。只能改变函数内部的this指向,并不意味着向对象里面加函数成员(调用别人类的函数)
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
fn(1,2); // window 3
o.fn(1,2) // undefined
2. apply方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
应用场景: 经常跟数组有关系
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn()// 此时的this指向的是window 运行结果为3
fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3
3. bind方法
bind() 方法不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产生的新函数
如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind
应用场景:不调用函数,但是还想改变this指向
var o = {
name: 'andy'
};
function fn(a, b) {
console.log(this);
console.log(a + b);
};
var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数
f();//调用新函数 this指向的是对象o 参数使用逗号隔开
4. call、apply、bind三者的异同
-
共同点 : 都可以改变this指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递。call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。
- bind 不会调用函数, 可以改变函数内部this指向.
-
应用场景
- call 经常做继承.
- apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
var min = Math.min.apply(null,a)- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.