js函数总结

116 阅读7分钟

函数的定义和调用

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指向.