一、函数属性和arguments

函数属性

默认函数对象中已经有的属性。

  • name
  • length
1
2
3
4
function foo(a,b,c) {}

console.log(foo.name)  // foo
console.log(foo.length)  // 3

arguments

一个对应于传递给函数的参数的类数组(array-like)对象。

1
2
3
4
5
function foo(){
  console.log(arguments)
}

foo(12,20,34)

image-20230623173501901

array-like意味着它不是一个数组类型,而是对象类型。

  • 但是它有数组的一些特性,比如length,比如可以通过index索引访问
  • 但是没有数组的方法,比如filter,map

argument转数组,以便使用数组的特性:

  • Array.from
  • […arguments]
  • [].slice.apply(arguments)

剩余参数(rest)

es6中引用了rest parameter,可以将不定数量的参数放到一个数组中。

如果最后一个参数是 … 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组。

注意:剩余参数需要写到其他参数的最后。

1
2
3
4
5
function foo(num, str, ...args) {
  console.log(args)
}

foo(1, "mocha", 123, 456)  // [123, 456]

剩余参数和argument的区别:

  • 剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参
  • arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作
  • arguments是早期ecmascript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是es6中提供并且希望以此来替代arguments的

二、纯函数的理解和应用

确定的输入,一定会产出生确定的输出。

函数在执行过程中,不能产生副作用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var names = ["abc", "cba", "nba", "mba"]

// 1、slice:纯函数 返回新的数组,对原数组无影响
var newNames = [].slice.apply(names, [1, 3])
console.log(newNames)  // [ 'cba', 'nba' ]
console.log(names)  // [ 'abc', 'cba', 'nba', 'mba' ]

// 2、splice: 非纯函数,直接操作数组
names.splice(2, 2)
console.log(names)  // [ 'abc', 'cba' ]

三、柯里化的理解和应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function foo1(x, y, z) {
  console.log(x + y + z)
}

// 函数柯里化
function foo2(x) {
  return function (y) {
    return function (z) {
	  console.log(x + y + z)
	}
  }
}

foo2(10)(20)(30)

// 箭头函数简写方式
var foo3 = x => y => z => console.log(x + y + z)
foo3(10)(20)(30)  // 60

自动柯里化函数:

 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 foo(x, y, z) {
  console.log(x + y + z)
}

// 自动柯里化
function curring(fn) {
  function curryFn(...args) {
    // 两类操作
    // 第一类操作:继续返回一个新的函数,继续接收参数
    // 第二类操作:直接执行fn函数

    if (args.length >= fn.length) {
      // 执行第二类
      // return fn(...args)
      return fn.apply(this, args)
    } else {
      // 执行第一类
      return function (...newArgs) {
        // return curryFn(...args.concat(newArgs))
        return curryFn.apply(this, args.concat(newArgs))
      }
    }
  }

  return curryFn
}


var fooCurry = curring(foo)
fooCurry(10)(20)(30)  // 60

四、组合函数理解和应用

组合函数是在JavaScript开发过程中对函数使用的一种技巧,模式:

  • 比如现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的
  • 那么如果每次都需要进行两个函数的调用,操作上就会显得重复
  • 那么可以将这两个函数组合起来,自动依次调用
  • 这个过程就是对函数的组合,称之为组合函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function double(num) {
  return num * 2
}

function pow(num) {
  return num ** 2
}

function composeFn(num) {
  return double(pow(num))
}

console.log(composeFn(4))  // 32

自动组合函数:

 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
function composeFn(...fns) {
  // 1、边界判断(edge case)
  var length = fns.length

  if (length <= 0) return

  for (var i = 0; i < length; i++) {
    if (typeof fns[i] !== "function") {
      throw new Error(`index position ${i + 1} must be function`)
    }
  }

  // 2、返回的新函数
  return function (...args) {
    var result = fns[0].apply(this, args)
    for (var i = 1; i < length; i++) {
      var fn = fns[i]
      result = fn.apply(this, [result])
    }
    return result
  }
}

function double(num) {
  return num * 2
}

function pow(num) {
  return num ** 2
}

var newFn = composeFn(double, pow)
console.log(newFn(100))  // 40000

五、with,eval的使用

with语句

扩展一个语句的作用域链

1
2
3
4
5
6
7
8
9
var obj = {
  name: "mocha",
  age: 18
}

with(obj) {
  console.log(name)  // mohca
  console.log(age)  // 18
}

不建议使用with语句,以为它可能是混淆错误和兼容性问题的根源.

eval函数

运行执行一个代码字符串。

  • eval是一个特殊的函数,它可以将传入的字符串当作JavaScript代码来执行

  • eval会将最后一行执行语句的结果,作为返回值

1
2
3
var evalString = `var message = "hello world"; console.log(message)`

eval(evalString)  // hello world

不建议在开发中使用eval:

  • eval代码的可读性非常差(代码的可读性是高质量代码的重要原则)
  • eval是一个字符串,那么有可能在执行的过程中被刻意篡改,造成被攻击的风险
  • eval的执行必须经过JavaScript解释器,不能被JavaScript引擎优化

六、严格模式的使用

JavaScript历史的局限性:

  • 长久以来,JavaScript不断向前发展并且未带来任何兼容性问题
  • 新的特性被加入,旧的功能也没有改变,这么做有利于兼容旧代码
  • 但缺点是JavaScript创造者的任何错误或不完善的决定也将永远被保留在JavaScript语言中

在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode)

  • 严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了“懒散(sloppy)模式”
  • 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行

严格模式对正常的JavaScript语义进行了一些限制:

  • 严格模式通过抛出错误来消除一些静默错误
  • 严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
  • 严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法

开启严格模式

  • 可以支持在js文件中开启严格模式

    1
    2
    3
    4
    
    // javascript文件
    "use strict"
    
    console.log(name) 
    

    image-20230703112623618

  • 也支持对某一个函数开启严格模式

    1
    2
    3
    4
    5
    6
    7
    8
    
    function foo() {
      "use strict"
    
      m = "foo"
      console.log(m) 
    }
    
    foo()
    

    image-20230703112713960

注意:

  • 没有类似于 “no use strict"这样的指令也可使程序返回默认模式
  • 现代JavaScript支持 “class” 和 “module”,它们会自动启用use strict

严格模式的语法限制:

JavaScript被设计未新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的

但是这种方式可能会带来安全隐患

在严格模式下,这种失误就会被当作错误,以便可以快速的发现和修正

  1. 无法意外的创建全局变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    "use strict"
    
    function foo() {
      message = "hello message"
    }
    
    foo()
    
    console.log(message)  // ReferenceError: message is not defined
    
  2. 严格模式会使引起静默失败的赋值操作抛出异常(silently fail,不报错也没有任何效果)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    'use strict'
    
    var obj = {
    	name: 'mocha'
    }
    
    Object.defineProperty(obj, 'name', {
    	configurable: false
    })
    
    delete obj.name  // 非严格模式下,此步不报错也没有任何效果,继续执行下一步;严格模式下抛出错误
    
    console.log(obj.name)
    

    image-20230703113559542

  3. 严格模式下试图删除不可删除的属性

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    "use strict"
    
    var obj = {
      name: "mocha"
    }
    
    Object.defineProperty(obj, "name", {
        writable: false
    })
    
    obj.name = "latte"
    
    console.log(obj.name)
    
  4. 严格模式不允许函数参数有相同的名称

    1
    2
    3
    
    "use strict"
    
    function foo(num, num) {}
    
  5. 不允许0的八进制语法

    1
    2
    3
    
    "use strict"
    
    console.log(0o123)
    
  6. 不允许使用with

    image-20230703113727798

  7. eval不再为上层引用变量

    1
    2
    3
    4
    5
    
    "use strict"
    
    eval(`var message = "hello message"`)
    
    console.log(message)  // ReferenceError: message is not defined
    
  8. this绑定不会默认转成对象

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    "use strict"
    
    function foo() {
      console.log(this)
    }
    
    foo.apply("abc")  // abc
    foo.apply(123)  // 123
    foo.apply(undefined)  // undefined
    foo.apply(null)  // null
    
    // 独立函数调用时,绑定window对象
    // 严格模式下,不绑定全局对象而是绑定undefined
    foo()  // undefined