天天看点

这些 function 的细节你都知道吗?

大家好,我是 ​​Lvzl​​, 一个三年工作经验的前端小菜鸡,在​​掘金​​平台分享一些 平时学习的感悟 & 实际项目场景 的文章。

本文主要内容:详细聊聊 ​

​JavaScript​

​ 函数。

函数概述

函数的声明语句

​function​

​​ 命令声明的代码区块就是一个函数。​

​function​

​ 命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数,函数体放在大括号里面。

以下面的例子来说:hello 是函数名、有一个参数、函数体是 ​

​console.log(a)​

function hello(a) {
  console.log(a)
}
// 调用:
hello('hello world')
复制代码      

函数表达式

采用变量赋值的写法:将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式。

const hello = function (a) {
  console.log(a)
}
// 将一个具名函数赋值给变量。
const hello = function fn(a) {
  console.log(a)
  console.log(fn) // fn();
  console.log(fn === hello) // true;
}
console.log(fn) // ReferenceError: fn is not defined;
复制代码      

具名函数​

​fn​

​​和​

​hello​

​​是同一个函数,但是作用范围不一致,​

​fn​

​​只能在函数体内使用,相当于函数的一个局部变量,​

​hello​

​可在函数内部,外部调用。

Function 构造函数

可以传递任意数量的参数给​

​Function​

​构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。

const add = new Function('x', 'y', 'return x + y')
//等同于
function add(x, y) {
  return x + y
}
复制代码      

Function构造函数可以不使用new命令,返回结果完全一样。

这些 function 的细节你都知道吗?

函数的返回值 return

​return​

​ 只能出现在函数体内。

  1. 一个函数中可以有多个​

    ​return​

    ​语句。
  2. ​return​

    ​​后没有任何返回值,(返回值为 ​

    ​undefined​

    ​​)代表直接提出函数执行,​

    ​return​

    ​​之后除了在 ​

    ​finally​

    ​中的代码,都不会再执行。如下图:
这些 function 的细节你都知道吗?
  1. ​return​

    ​可以返回任何数据类型的数据。
  2. 如果函数调用时在前面加上了​

    ​new​

    ​​前缀,且返回值不是一个对象或者没有返回值,则返回​

    ​this​

    ​(该新对象),如下图:
这些 function 的细节你都知道吗?

函数调用

函数调用模式

使用函数调用模式调用函数时,非严格模式下,​

​this​

​​被绑定到全局对象;在严格模式​

​(use strict;)​

​​下,​

​this​

​​是​

​undefined​

​。

非严格模式: 

这些 function 的细节你都知道吗?

严格模式:

这些 function 的细节你都知道吗?

方法调用模式

当一个函数被保存在对象的一个属性时,我们称它为一个方法。当一个方法被调用时,​

​this​

​被绑定到该对象。如果 调用表达式包含一个提取属性的动作,那么它就是被当做一个方法来调用。

var p = {
  a: 1,
  fn: function () {
    this.a = 2
  }
}
console.log(p.a) // 1
p.fn()
console.log(p.a) // 2
复制代码      

构造器调用模式

如果函数或者方法调用之前带有关键字 ​

​new​

​,它就构成构造函数调用。

  • 如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内。
  • 如果构造函数没有形参,​

    ​JavaScript​

    ​ 构造函数调用的语法是允许省略实参列表和圆括号的。凡是没有形参的构造函数调 用都可以省略圆括号。
const o = new Object() 
// 等价于 
const o = new Object
复制代码      

间接调用模式

​JavaScript​

​​ 中函数也是对象,函数对象也可以包含方法。​

​call​

​​ 和 ​

​apply​

​​方法可以用来间接地调用函数。 这两个方法都允许显式指定调用所需的​

​this​

​值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。

  • ​call​

    ​方法使用它自有的实参列表作为函数的实参。
  • ​apply​

    ​法则要求以数组的形式传入参数。
const obj = {}
function sum(x, y) {
  return x + y
}
console.log(sum.call(obj, 1, 2)) //3
console.log(sum.apply(obj, [1, 2])) //3
复制代码      

函数参数

​JavaScript​

​​是弱类型语言,函数定义时未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上​

​JavaScript​

​函数调用甚至不检查传入形参的个数。

参数个数

1.当实参(函数被调用时掺入的实际参数值)比函数声明指定的形参(函数定义时的参数列表)个数要少,剩下的形参都将设置为 undefined 值。
function add(x, y) {
  return x + y // x:1, y: undefined
}
add(1)
复制代码      
2.当实参比形参个数要多时,剩下的实参没有办法直接获得,需要使用 ​

​arguments​

​​ 对象来获取。​

​arguments​

​​是个类数组,有数组的部分属性,如​

​length​

​,可以通过索引去获取对应的参数列表。
function add(x, y) {
  console.log(arguments)
  return x + y
}
add(1, 2, 3, 4, 5)

// Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
复制代码      
3.函数定义时也可以不给形参,到时直接通过​

​arguments[索引]​

​去获取实参。

同名形参

在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。

function add(x, x, x) {
  return x
}
console.log(add(1, 2, 3)) // 3
// 严格模式编译报错。
复制代码      

函数重载

在​

​Java​

​语言中,函数的重载是这样定义的:方法名相同,参数的个数或者类型必须不同。

​JavaScript​

​​函数不能像​

​Java​

​上那样实现重载。只能通过检查传入函数中参数的类型和数量并作出不同的反应,来模仿方法的重载。

function doAdd() {
  if (arguments.length == 1) {
    alert(arguments[0] + 10)
  } else if (arguments.length == 2) {
    alert(arguments[0] + arguments[1])
  }
}
复制代码      

参数传递

值传递:对于基本数据类型的参数传递。比如​

​String​

​​,​

​Number​

​​,​

​Boolean​

​​等。在向参数传递基本类型的值时,被传递的值会被复制到一个局部变量(命名参数或 ​

​arguments​

​对象的一个元素)。
function addTen(num) {
  num += 10
  return num
}
var count = 20
var result = addTen(count)
console.log(count) //20,没有变化
console.log(result) //30
复制代码      
引用传递:参数为引用类型的数据时​

​(Object、Array、......)​

​,传递过去的是引用数据的内存地址。会把这个地址复制给一个局部变量,因此这个局部变量的变化会直接改变指向该内存地址的引用数据。
function setName(obj) {
  //obj 在函数内部是一个局部变量
  obj.name = 'test'
}
var person = new Object()
setName(person)
console.log(person.name) //'test'
复制代码      

函数属性和方法

函数是 ​

​JavaScript​

​​ 中特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样。甚至可以用​

​Function​

​构造函数来创建新的函数对象。

属性

  • length:​

    ​arguments​

    ​​对象的​

    ​length​

    ​​属性表示实参个数,而参数的​

    ​length​

    ​属性则表示形参个数。 
  • prototype:每一个函数都有一个​

    ​prototype​

    ​​属性,这个属性指向了一个对象的引用,这个对象叫做原型对象(​

    ​prototype object​

    ​)。每一个函数都包含不同的原型对象。将函数用作构造函数时,新创建的对象会从原型对象上继承属性。
function fn() {}
var obj = new fn()
fn.prototype.a = 1
console.log(obj.a) //1
复制代码      
  • name:函数定义了一个非标准的​

    ​name​

    ​​属性,通过这个属性可以访问到给定函数指定的名字,这个属性的值永远等于跟在​

    ​function​

    ​​关键字后面的标识符,匿名函数的 ​

    ​name​

    ​属性为空。

方法

每一个函数都包含两个非继承而来的方法:​

​apply​

​​和​

​call​

​方法。这两个方法的用途都是在特定的作用域中调用函数。

调用方式:

  • func.apply(作用域对象, [a,b,c]);
  • func.call(作用域对象, a,b,c);

以对象 o 的方法来调用函数 f(),可以使用 call()和 apply():

window.color = "red";
var o = {color: "blue"};
function sayColor(){
  console.log(this.color);
}
sayColor();      //red
sayColor.call(this);  //red
sayColor.call(window); //red
sayColor.call(o);   //blue
sayColor.call(o) 

// 等价于:
o.sayColor = sayColor;
o.sayColor();  //blue
delete o.sayColor;
复制代码      

在非严格模式下,使用函数的​

​call​

​​或​

​apply​

​​方法时,​

​null​

​​或​

​undefined​

​​值会被转换为全局对象。而在严格模式下,函数的​

​this​

​值始终是指定的值。

应用:

找出数组中最大元素。
const a = [10, 2, 4, 15, 9]
Math.max.apply(null, a) // 15
// es6
Math.max(...a)
复制代码      
将类数组转成真正的数组。
var add = function(x,y){
  console.log(Array.prototype.slice.apply(arguments));
  // es6
  Array.from(arguments)
};
add(1,2); // [1, 2]
复制代码      
将一个数组的值 push 到另一个数组中。
var a = []
Array.prototype.push.apply(a, [1, 2, 3])
// es6
a.push(...[1,2,3])
console.log(a) //[1,2,3]
复制代码      
function getConfig(colors, size, otherOptions) {
  console.log(colors, size, otherOptions)
}
var defaultConfig = getConfig.bind(null, '#c00', '1024*768')
defaultConfig('123') //'#c00 1024*768 123'
defaultConfig('456') //'#c00 1024*768 456'      

继续阅读