js基础(八) 函数

7/30/2017 javaScript面试

# 知识点一 函数

  • 一个函数存在了多面性
    1. 普通函数:它本是就是一个普通的函数,执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
    2. :它自己的实例,也有一个叫做 prototype 属性是自己的原型,它的实例都是指向自己的原型
    3. 普通对象:和 var obj = {} 中的 obj 一样,就是一个普通的对象,它作为对象可以有一些自己的私有属性,也可以通过 __proto__ 找到 Function.prototype 上的公用属性
    4. 这三者之间没有任何关系
function Fn() {
  var num = 500
  this.x = 100
}
Fn.prototype.getX = function() {
  console.log(this.x)
}
Fn.aaa = 1000
var f = new Fn // Fn中的this是f

// 实例-->类
f.num // undefined
f.aaa // undefined

// 普通函数
var res = Fn() // Fn中的this是window
console.log(res) // undefined

// 普通对象
Fn.aaa // 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 知识点二 函数属性

  1. length :形参的个数
  2. name :函数名
  3. prototype :类的原型,在原型上定义的方法都是当前这个类实例的公有方法
  4. __proto__ :把函数当作一个普通的对象,指向 Function 这个类的原型

# 知识点三 函数与对象关系图

下图中的函数与对象:

  • 函数: FnFunctionObject
  • 对象: fFnFn.prototypeFunctionFunction.prototypeObjectObject.prototype

万物皆对象

image.png

# 知识点四 call

  • 作用:首先让原型上的 call 方法执行,在执行 call 方法的时候,让方法中的 this 指向为第一个参数,然后再把函数执行
var obj = { name: 'obj' }
function fn() {
  console.log(this, arguments)
}
fn(1, 2, 3) // --> window
fn.call(obj, 1, 2, 3) // --> obj
1
2
3
4
5
6
  • 手写内置的 call 方法
Function.prototype.myCall = function() {
  // 1.让函数中的this指向第一个参数
  var args = Array.from(arguments)
  var context = args.length > 0 ? args.shift() : window
  context.fn = this
  // 2.执行函数
  context.fn(...args)
  delete context.fn
}

function fn() {
  console.log(this, arguments)
}

fn(1, 2, 3) // --> window
fn.myCall({name: 'fn'}, 1, 2, 3) // --> {name: 'fn'}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • call(thisArg, arg1, arg2, ...) 方法第一个参数 thisArg 是函数运行时 this 的指向, arg1, arg2, ... 是传递给函数的参数列表,参数列表 必须列举出来

  • 案例:

    fn1.call 首先 fn1 通过原型链找到 Function.prototype 上的 call 方法,然后在让 call 方法通过原型链找到 Function.prototypecall (因为 call 本身的值也是一个函数,所以同样可以找到Function.prototype),在带二次再找到 call 的时候方法执行,方法中的 thisfn1.call ,首先让这个方法中的 this 变为 fn2 ,然后再让 fn1.call 执行,打印出 window, 2

function fn1() {
  console.log(this, 1)
}
function fn2() {
  console.log(this, 2)
}

fn1.call.call(fn2) // --> window, 2

// call实现的为代码
// Function.prototype.call = function() {
//      // .....
//      this()
// }

// 分析:
// 1. fn1.call --> Function.prototype.call
// 2. fn1.call.call(fn2) --> Function.prototype.call.call(fn2),
//    先让第二个call执行,call中的this是 Function.prototype.call,
//    然后让 Function.prototype.call 中的this变为 fn2,再让 Function.prototype.call 执行
// 3. 最后fn2()执行,打印出 window, 2

Function.prototype.call(fn1)
// 分析:
// 1. Function.prototype 是一个匿名函数或空函数
// 2. 先让call执行,call中的 this 指向 fn1 再让 Function.prototype 执行即匿名函数或空函数执行
// 2. 最后返回 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • 严格模式
"use strict"

function fn(num) {
  console.log(this, num)
}

fn.call() // 在严格模式下this --> undefined
fn.call(null) // 在严格模式下this --> null
fn.call(undefined) // 在严格模式下this --> undefined
1
2
3
4
5
6
7
8
9

# 知识点五 apply

  1. 核心:和 call 方法的作用一样,在执行 apply 方法的时候,让方法中的 this 指向为第一个参数,然后再把函数执行
  2. 语法: func.apply(thisArg, [argsArray])
  3. call 的异同点:
    • call 一样的是,方法第一个参数 thisArg 是函数运行时 this 的指向
    • call 不一样的是 argsArray 是传递给函数的参数列表,参数列表 必须是数组
function fn(a, b) {
  console.log(this, a, b)
}
var obj = {
  name: 'apply'
}
fn.apply(obj, [1, 2])
1
2
3
4
5
6
7

# 知识点六 bind

  1. 核心: bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用,参数列表 必须列举出来
  2. 语法: function.bind(thisArg[, arg1[, arg2[, ...]]])
function fn(a, b) {
  console.log(this, a, b)
}
var obj = {
  name: 'apply'
}
var f = fn.bind(obj, 1, 2)
f()
1
2
3
4
5
6
7
8

# 知识点七 括号表达式

  1. 括号表达式,里面有多项,只执行最后一项
  2. 括号里面有多项,只执行最后一项时,最后一项函数里面的 thiswindow
function fn1() {
  console.log(this, 'fn1')
}
function fn2() {
  console.log(this, 'fn2')
}
var obj = { 
  name: 'obj',
  fn: fn2
}
(fn2, obj.fn)() // 最后一项执行,this是window,因为相当于拷贝了一份 obj.fn 函数在这里
(obj.fn)() // this 是 obj
1
2
3
4
5
6
7
8
9
10
11
12

# 知识点八 回调函数

# 传参方式

  • 将回调函数的参数作为与回调函数同等级的参数进行传递

image

  • 回调函数的参数在调用回调函数内部创建

image

# 注意

  • 匿名回调函数的中 thiswindow

# 案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // Polyfill
    if(!!Array.prototype.forEach) {
      Array.prototype.forEach = function(callback, thisArg) {
        if (typeof callback !== 'function') {
          throw new TypeError(callback + ' is not a function')
        }
        const context = thisArg || window
        for (let i = 0; i < this.length; i++) {
          callback && callback.call(context, this[i], i, this)
        }
      }
    }
    if(!Array.prototype.map) {
      Array.prototype.map = function(callback, thisArg) {
        if (typeof callback !== 'function') {
          throw new TypeError(callback + ' is not a function')
        }
        const context = thisArg || window
        const res = []
        for (let i = 0; i < this.length; i++) {
          res[i] =  callback && callback.call(context, this[i], i, this)
        }
        return res
      }
    }
    const obj = { name: 'test' }
    const aryCustom = [12, 23, 34, 45, 56]
    aryCustom.forEach(function(item, index, input) {
      console.log(item, index, input, this)
    }, obj)
    
    const resCustom = aryCustom.map(function(item, index, input) {
      console.log(item, index, input, this)
      return item * 10
    }, obj)
    console.log(aryCustom)
    console.log(resCustom)
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 知识点九 柯里化函数

柯里化函数思想:一个js预处理的思想

核心思想:利用函数执行可以形成一个不销毁的私有作用域的原理,把需要预处理的内容都存在这个不销毁的作用域中,并且返回一个小函数,以后我们执行的都是小函数,在小函数中把之前预存储的值进行相关的操作处理即可

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      height: 100vh;
      width: 100vw;
    }
  </style>
</head>
<body>
  <script>
    function bind() {
      const args = arguments
      let callback = null
      let thisArg = window
      let rest = []

      if (args.length === 0) {
        throw new Error('bind need a function argument')
      } else if (args.length === 1) {
        callback = arguments[0]
        if (typeof callback !== 'function') {
          throw new TypeError(callback + ' is not a function')
        }
      } else if (args.length >= 2) {
        callback = arguments[0]
        thisArg = arguments[1]
        if (Array.prototype.toString.call(thisArg) !== '[object Object]') {
          throw new TypeError(thisArg + ' is not a object')
        }
        rest = Array.prototype.slice.call(arguments, 2)
      }

      thisArg.fn = callback
      return function() {
        thisArg.fn(...rest, ...arguments)
        delete thisArg.fn
      }
    }

    const obj = { name: 'test' }
    function fn(num1, num2) {
      console.log(arguments, this)
    }
    // window.setTimeout(fn.bind(obj), 0)
    // window.setTimeout(bind(fn, obj, 100), 0)
    
    // document.body.onclick = function() {
    //   console.log(arguments)
    // }
    document.body.onclick = bind(fn, obj, 100, 200)
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 知识点十 函数编程

函数式编程指北 (opens new window)

# 知识点十一 案例

# 案例一 求数组最大最小值

// 方式一
var ary = [22, 34, 56, 43, 12, 99, 78, 23, 19]
ary.sort((a, b) => a -b)
var max = ary[ary.length - 1]
var min = ary[0]
console.log(max, min)

// 方式二
var ary2 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
var max2 = eval(`Math.max(${ary2.join()})`)
var min2 = eval(`Math.min(${ary2.join()})`)
console.log(max2, min2)

// 方式三
var ary3 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
var max3 = Math.max.apply(null, ary3)
var min3 = Math.min.apply(null, ary3)
console.log(max3, min3)

// 方式四
var ary4 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
var max4 = Math.max.call(...ary4)
var min4 = Math.min.call(...ary4)
console.log(max4, min4)

// 方式五
var ary5 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
var max5 = Math.max(...ary5)
var min5 = Math.min(...ary5)
console.log(max5, min5)

// 方式六
var ary6 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
var max6 = ary6[0]
var min6 = ary6[0]
for(var i = 1; i < ary6.length; i++) {
  var cur = ary6[i]
  max6 = cur > max6 ? cur : max6
  min6 = cur < min6 ? cur : min6
}
console.log(max6, min6)

// 方式七
var ary7 = [22, 34, 56, 43, 12, 99, 78, 23, 19]
function getMax(prev, next) {
    return Math.max(prev, next)
}
function getMin(prev, next) {
    return Math.min(prev, next)
}
var mxa7 = ary7.reduce(getMax)
var min7 = ary7.reduce(getMin)
console.log(mxa7, min7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 案例二 求平均数

// 方式一
function avgFn() {
  var args = []
  for(i = 0;i < arguments.length;i++) {
    args.push(arguments[i])
  }
  args.sort((a, b) => a-b)
  args.pop()
  args.shift()
  var res = eval(args.join('+')) / args.length
  return res.toFixed(2)
}
var avg = avgFn(9.8, 9.7, 10, 9.9, 9.0, 9.8, 3.0)
console.log(avg)

// 方式二
function avgFn() {
  // var args = [].slice.apply(arguments)
  var args = Array.prototype.slice.apply(arguments)
  args.sort((a, b) => a-b)
  args.pop()
  args.shift()
  var res = eval(args.join('+')) / args.length
  return res.toFixed(2)
}
var avg = avgFn(9.8, 9.7, 10, 9.9, 9.0, 9.8, 3.0)
console.log(avg)

// 方式三
function avgFn() {
  // var args = [].slice.call(arguments)
  var args = Array.prototype.slice.call(arguments)
  args.sort((a, b) => a-b)
  args.pop()
  args.shift()
  var res = eval(args.join('+')) / args.length
  return res.toFixed(2)
}
var avg = avgFn(9.8, 9.7, 10, 9.9, 9.0, 9.8, 3.0)
console.log(avg)

// 方式四
function avgFn() {
  [].sort.call(arguments, (a, b) => a-b);
  [].pop.call(arguments);
  [].shift.call(arguments);
  var res = eval([].join.call(arguments, '+')) / arguments.length
  return res.toFixed(2)
}
var avg = avgFn(9.8, 9.7, 10, 9.9, 9.0, 9.8, 3.0)
console.log(avg)

// 方式五
function avgFn() {
  var args = Array.from(arguments)
  args.sort((a, b) => a-b)
  args.pop()
  args.shift()
  var res = args.reduce((pre, next) => pre + next) / args.length
  return res.toFixed(2)
}
var avg = avgFn(9.8, 9.7, 10, 9.9, 9.0, 9.8, 3.0)
console.log(avg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 案例三 类数组转数组

var utils = {
  listToArray: function(likeAry) {
    var ary = []
    try {
      ary = Array.prototype.slice.call(likeAry)
    } catch(e) {
      for(var i = 0;i < likeAry.length;i++) {
        ary[ary.lenght] = likeAry[i]
      }
    }
    return ary
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 参考

理解与使用Javascript中的回调函数 (opens new window)

函数式编程指北 (opens new window)

函数式编程入门教程 (opens new window)

Pointfree 编程风格指南 (opens new window)

ES5和ES6继承的执行顺序和区别 (opens new window)