# 函数

# 参数求和

function sum() {
  var total=null;
  for (var i = 0; i < arguments.length; i++) {
    var val = Number(arguments[i]);
    if (isNaN(val)) {
      continue;
    }
    total+=val
    console.log(total);
  }
  return total;
}
sum(6,8,78,'90')
sum('a',8,'-0','66')
1
2
3
4
5
6
7
8
9
10
11
12
13
14

js执行循序

  • 开辟一个全局作用域(window)
  • 在当前作用域(window)下进行预解析(带var和function),var和function预解释不一样,var只是提前声明,function提前声明和定义都完成了
  • 开始执行代码,遇到function定义直接跳过。所以函数定义可以写在后面,预解释已经声明定义了
  • 每个函数执行时会形成一个新的私有作用域(栈内存),在这个作用域内在进行预解释,然后在函数体内从上到下执行。在函数的这个私有作用域中定义的变量都是私有变量。形成的这个作用域保护里面的私有变量不受外界干扰,这种机制叫做闭包。函数执行一次就会形成一个新的私有作用域,重复这次步骤。
  • 一般情况下,每一次函数执行完成后,函数新形成的作用域会自定销毁

注意:预解释是发生在当前作用域下的

# 自执行函数

定义和执行同时完成

;(function (name) {
  console.log(name);
})('newming');

~function (name) {
  console.log(name);
}('newming');

-function (name) {
  console.log(name);
}('newming');

!function (name) {
  console.log(name);
}('newming');

+function (name) {
  console.log(name);
}('newming');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 函数柯里化

// bind 改变 this 指向,返回一个新的函数,不执行
function add (a, b, c) {
  return a + b + c
}

var func = add.bind(undefined, 100)
func(1, 2) // 103

var func2 = func.bind(undefined, 200)
func2(10) // 310
1
2
3
4
5
6
7
8
9
10

# bind 与 new

function foo () {
  this.b = 100
  return this.a
}

var func = foo.bind({a: 1})

func() // 1
new func() // {b: 100}
// 注意 new 的时候会忽略 return 和 bind
1
2
3
4
5
6
7
8
9
10

# bind polyfill

具体实现原理会写篇文章论述一下,结合上边 bind 与 new 的代码

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          console.log(this instanceof fNOP) // 当通过 new 创建时为 true
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}
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

# 函数预解析

function test (a, b) {
  var c = 10
  function d () {}
  var e = function _e () {} // 函数表达式不会影响 VO
  (function x () {}) // 扩起来了,成为表达式,但没有执行,忽略了
  b = 20
}

text(10)
// 此时函数的执行上下文中进行的初始化为(AO 激活上下文, VO 执行上下文)
AO(text) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <ref to func 'd'>,
  e: undefined
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

VO 按照如下顺序进行填充:

  • 函数参数(如未传入,初始化该参数值为 undefined)
  • 函数声明(若发生命名冲突,会覆盖)
  • 变量声明(初始化变量值为 undefined,若发生命名冲突,会忽略)
// 1. 函数声明命名冲突,函数声明覆盖参数
function test1 (x) {
  function x () {}
  console.log(x)
}
test1(100) // f x() {}

// 2. 函数声明与变量声明冲突,变量声明忽略,注意是初始化阶段
function test2 () {
  function x () {}
  var x
  console.log(x)
}
test2() // f x () {} 说明 var x 被忽略了

function test3 () {
  function x () {}
  var x = 10
  console.log(x)
}
test3() // 10 var x 被忽略了,不过已经过了初始化阶段,在代码执行时覆盖了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 原型链小例

OOP 原型链通过 Object.create() 继承

function Person (name, age) {
  this.name = name
  this.age = age
}

Person.prototype.hi = function () {
  console.log('Hi, my name is ' + this.name + ', i am ' + this.age + ' years old.')
}

Person.prototype.LEGS_NUM = 2
Person.prototype.ARMS_NUM = 2
Person.prototype.walk = function () {
  console.log(this.name + ' is walking...')
}

function Student (name, age, className) {
  Person.call(this, name, age)
  this.className = className
}

// 通过 Object.create 继承,不会破坏 Person 的原型
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
// 改写 hi 方法,不会破坏 Person 的 hi 方法
Student.prototype.hi = function () {
  console.log(`Hi, my name is ${this.name}, i am ${this.age} years old now, and from ${this.className}.`)
}

Student.prototype.learn = function (subject) {
  console.log(`${this.name} is learning ${subject} at ${this.className}.`)
}

// 测试
var newming = new Student('Newming', 25, 'Class 39')
newming.hi() // Hi, my name is Newming, i am 25 years old now, and from Class 39.
newming.LEGS_NUM // 2
newming.walk() // Newming is walking...
newming.learn('Math') // Newming is learning Math at Class 39.
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

# 模拟重载

根据参数列表的不同,执行不同的操作

function Person () {
  var args = arguments
  if (typeof args[0] === 'object' && args[0]) {
    if (args[0].name) {
      this.name = args[0].name
    }
    if (args[0].age) {
      this.age = args[0].age
    }
  } else {
    if (args[0]) {
      this.name = args[0]
    }
    if (args[1]) {
      this.age = args[1]
    }
  }
}

Person.prototype.toString = function () {
  return 'name=' + this.name + ',age=' + this.age
}

var newming = new Person('Newming', 25)
newming.toString() // name=Newming,age=25

var newm = new Person({name: 'Newming', age: 25})
newm.toString() // name=Newming,age=25
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

调用子类方法

function Person (name) {
  this.name = name
}

function Student (name, className) {
  this.className = className
  Person.call(this, name)
}

var bosn = new Student('Bosn', '39')
bosn // Student {className: 39, name: 'Bosn'}

Person.prototype.init = function () {}

Student.prototype.init = function () {
  // do sth...
  Person.prototype.init.apply(this, arguments)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 链式调用

function ClassManager () {}
ClassManager.prototype.addClass = function (str) {
  console.log('class: ' + str + ' added.')
  return this
}

var manager = new ClassManager()
manager.addClass('classA').addClass('classB').addClass('calssC')
// class: classA added.
// class: classB added.
// class: calssC added.
1
2
3
4
5
6
7
8
9
10
11

# 探测器例子

!function (global) {
  function DetectorBase (configs) {
    if (!this instanceof DetectorBase) {
      throw new Error('Do not invoke without new.')
    }
    this.configs = configs
    this.analyze()
  }
  DetectorBase.prototype.detect = function () {
    throw new Error('Not implemented')
  }
  DetectorBase.prototype.analyze = function () {
    console.log('analyze...')
    this.data = '###data###'
  }

  function LinkDetector (links) {
    if (!this instanceof LinkDetector) {
      throw new Error('Do not invoke without new.')
    }
    this.links = links
    DetectorBase.apply(this, arguments)
  }

  function ContainerDetector (containers) {
    if (!this instanceof ContainerDetector) {
      throw new Error('Do not invoke without new.')
    }
    this.containers = containers
    DetectorBase.apply(this, arguments)
  }

  // inherit first
  inherit(LinkDetector, DetectorBase)
  inherit(ContainerDetector, DetectorBase)

  // expand later
  LinkDetector.prototype.detect = function () {
    console.log('Loading data:' + this.data)
    console.log('Link detection started')
    console.log('Scaning links:' + this.links)
  }
  ContainerDetector.prototype.detect = function () {
    console.log('Loading data:' + this.data)
    console.log('Container detection started')
    console.log('Scaning containers:' + this.containers)
  }

  // prevent from being altered
  Object.freeze(DetectorBase)
  Object.freeze(DetectorBase.prototype)
  Object.freeze(LinkDetector)
  Object.freeze(LinkDetector.prototype)
  Object.freeze(ContainerDetector)
  Object.freeze(ContainerDetector.prototype)

  // export to global object
  Object.defineProperties(global, {
    LinkDetector: {value: LinkDetector},
    ContainerDetector: {value: ContainerDetector},
    DetectorBase: {value: DetectorBase}
  })

  // 继承基类
  function inherit (subClass, superClass) {
    subClass.prototype = Object.create(superClass.prototype)
    subClass.prototype.constructor = subClass
  }
}(this)

var cd = new ContainerDetector('#abc #def #ghi')
var ld = new LinkDetector('http://www.taobao.com http://www.tmall.com http://www.baidu.com')

cd.detect()
ld.detect()
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
64
65
66
67
68
69
70
71
72
73
74
75