在 JavaScript 中的始皇 一文中,笔者有个观点:
Object.prototype 是真正的始皇,任何原型都源自它;而 Function.prototype 是仅次于 Object.prototype 的存在,它是内置构造函数的创建者,任何构造函数都源自它
所以 Function 的原型有肯定的重要性,Function(构造函数)与 Function.prototype(原型)又是相生相伴的关系,从构造函数层面,它曾经比 Array、String、Number 等重要了,尽管比不上 Object,但也是仅次于它的存在
不仅如此,函数还能做很多事件。首先,它是个对象,这一知识点咱们在 所有皆对象 中解释过,所以它和对象一样,也有属性,也能够赋值给变量。除此之外,函数能够本身当作参数传递,也具备返回值的个性
总之,对象能做的它都能做,它还有本身的个性,能做更多的事件(例如:能作为参数传递,有返回值)
在解释函数的个性之前,先理解下它的属性和办法
属性和办法
看例子谈话
function func() {}
console.dir(func)
咱们用函数申明的模式创立了一个一般函数 func,打印它。尽管咱们没有对其进行任何的赋值操作,但它本身自带了各种属性,很显然,Function 是没有静态方法的,它只有实例属性和实例办法,都继承自 Function.prototype。咱们看到函数 func 上有 arguments
、caller
、length
、name
,这些都是继承自 Function.prototype,在 func.__proto__
中你能找到同样的属性,这其中的机密是 Function.__proto__ === Function.prototype
,具体可看 JavaScript 中的始皇 理解
实例属性
Function.prototype.arguments
:对应传递给函数的参数数组Function.prototype.constructor
:指向构造函数Function.prototype.length
:参数数量
实例办法
Function.prototype.apply(thisArg [, argsArray])
:调用一个函数并将其this
值设置为提供的传参,第二个参数以数组对象传入Function.prototype.call(thisArg [, arg1, arg2, ...argN])
:调用一个函数并将其this
值设置为提供的传参,也能够抉择传输新参数Function.prototype.bind(thisArg[, arg1[, arg2[, ...argN]])
:创立一个新函数,该函数在调用时,会将 this 设置为提供的thisArg
。在调用新绑定的函数时,可选的参数序列([, arg1[, arg2[, ...argN]]]
)会被提前增加到参数序列中Function.prototype.toString()
:返回示意函数源码的字符串。笼罩了Object.prototype.toString
办法
更多信息能够查看 MDN
理解 Function 的实例属性和办法后,咱们去看看如何创立函数
创立函数
创立函数有四种办法:函数构建函数、函数申明、函数表达式、箭头函数
// 函数构造函数:最初一个参数为函数逻辑,之前的都是参数
var add = new Function('x', 'y', 'return x + y');
// 函数申明
function add2(x, y) {return x + y;}
// 函数表达式
var add3 = function (x, y) {return x + y;};
// 箭头函数
var add4 = (x, y) => x + y
这里须要阐明的是,在失常开发中,函数构造函数根本用不到。开发中用的比拟多的是函数申明、函数表达式、箭头函数,那么三者有什么区别呢?
先比照函数申明和函数表达式
- 函数申明会引起函数晋升(且优先级比变量晋升高)
再比照箭头函数与一般函数
- 没有 this,函数体内的 this 需在内部词法环境中查找
- 没有 arguments
- 不能够当作构造函数。也就是说,箭头函数不能应用 new 命令,否则会抛出一个谬误
- 没有 super
- 不能够应用 yield 命令,因而箭头函数不能用作 Generator 函数
- 返回对象时必须在对象里面加上括号
创立函数就是如此,创立了如何调用函数呢?
调用函数
在不同的场景下,调用函数各显不同,以下几种为调用函数的形式
- 作为函数
- 作为办法
- 作为构造函数
- 应用 call/apply/bind
- 自调用函数
// 作为函数
var func1 = function () {return 'foo';};
console.log(func1); // foo
// 作为办法,即对象中的函数被称为办法
var obj1 = {func2: function () {return 'bar';},
};
console.log(obj1.func2()); // bar
// 作为构造函数
function Person() {
this.name = 'johnny'
this.age = 28;
this.gender = 'female';
this.getName = function () {return this.name;};
}
var cody = new Cody(); // 调用构造函数
console.log(cody);
// 应用 call/apply 调用
var obj2 = {sayHello: function () {console.log(this.name, arguments[0], arguments[1]);
},
};
var johan = {name: 'johan'};
var elaine = {name: 'elaine'};
// 在 johan 对象上调用 sayHello
obj2.sayHello.call(johan, 'foo', 'bar'); // johan, foo, bar
obj2.sayHello.apply(elaine, ['foo', 'bar']); // elaine, foo, bar
// 自调用
(function() {console.log('自调用函数');
})();
无论是创立函数,还是调用函数,能有什么用,能证实函数是一等公民吗?
函数为什么是一等公民
接下来,咱们来解释为什么说函数是一等公民?
首先,函数是对象,这意味着函数能够存储在一个变量、数组或对象中。其次,因为是对象,所以它也领有对象的个性,即它领有属性。除了对象的特色外,作为函数自身,它能够作为参数传递,也能够作为返回值返回。如此,这些因素就形成了函数成为 JavaScript 中的”一等公民“
// 作为变量保留变量、数组、对象
var funcA = function () {} // 作为变量
var funcB = [function(){}] // 作为数组变量
var funcC = {method: function() {}} // 作为对象办法
// 函数也是对象,意味着能够领有属性
var funcD = function () {}
funcD.name = 'funcD' // 赋值 name
console.log(funcD.name) // funcD
// 作为参数
var funcE = function(func) {func()
}
funcE(function () {console.log('函数作为参数传递')
})
// 作为函数返回值
var funcF = function (x, y) {return x + y // 函数个性,有返回值}
console.log(funF(1,2)) // 3
PS:所谓的一等公民,即 first-class function,也被称为头等函数,维基百科上对其的介绍是:
函数能够作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中
同理,MDN 也是同样介绍
其中函数作为参数传递和有返回值的个性,使其成为函数式编程的根底
因为函数不仅有对象的能力,而且还有参数传递和有返回值的独有个性,所以使得它成为一等公民。不仅如此,函数还有其余的个性
函数的其余个性
函数作用域:JavaScript 中的作用域分为全局作用域、函数作用域和块级作用域,块级作用域是 ES6 之后呈现的解决变量晋升存在变量笼罩、变量净化等设计缺点而出的个性。在此之前,只有全局作用域和函数作用域,全局只有一个作用域好了解。函数作用域是意识 JavaScript 重要知识点——闭包的根底,无关作用域的知识点,能够看这篇文章——作用域(后续文章更新)
this:this 是什么,在写原型、构造函数时,咱们已经在构造函数中应用过 this,并在 new 实例化它时,说 new 关键字会将构造函数中的 this 指向新对象并执行构造函数中的代码,那么 this 和什么无关呢?
它和作用域有点像,但不完全一致,它是与执行上下文绑定的,咱们会先聊 this 关键字(后续文章更新),再次根底上会衍生出 call、apply、bind 三大将(后续文章更新)。再回头看 执行上下文,不过再聊它之前,先把 词法环境(后续文章更新)讲清楚,之后再说 执行上下文与执行栈(后续文章更新)
讲了作用域、就会衍生出作用域链。讲了 this 关键字,就会引出执行上下文,两者一联合,就解释了闭包(后续文章更新),闭包是 JavaScript 中的难点。如果说原型是”少女杀手“,那么闭包就是”师母杀手“
函数有多种形式,如 IIFE,即 立刻执行函数(后续文章更新),为什么它这么做,这么做为了防止变量被净化。而后的 AMD/CMD,ES 中的模块化,都是为了让代码能独立不被别的文件影响
总之,函数有很多个性,因为这些个性,函数能力成为在 JavaScript 中叱诧风云的”人物“
总结
咱们就函数的属性、办法说起,介绍了 Function 内置的属性和办法,这样是为了不便开发者调用。接着咱们介绍如何创立函数,介绍了四种办法,创立了函数就调用函数,分五种状况介绍。最初咱们介绍了函数为什么成为一等公民。成为一等公民,首先是因为它是对象,领有对象的”能力“,其次,它本身有一些个性让其变得举世无双,例如能作为参数传递,有返回值,这两者是函数式编程的根底
你认为函数就这么简略?那你小瞧函数了
函数的个性还有函数作用域,绝对全局作用域,块级作用域,函数作用域的理论用途高达 90%;还有 this,Function 的原型办法中的 call/apply/bind 就是为了批改 this 而存在的,阐明批改 this 指向是个高频操作,this 的解释会引出执行上下文,与作用域中的作用域链联合,就能解释闭包行为。闭包又能衍生出词法环境、执行上下文与调用栈、以及闭包的利用防抖与节流、柯里化。垃圾回收机制等等
总之,函数在 JavaScript 的位置是很高的
参考资料
- 如何了解在 JavaScript 中 “ 函数是第一等公民 ” 这句话?
系列文章
- 深刻了解 JavaScript- 开篇
- 深刻了解 JavaScript-JavaScript 是什么
- 深刻了解 JavaScript-JavaScript 由什么组成
- 深刻了解 JavaScript- 所有皆对象
- 深刻了解 JavaScript-Object(对象)
- 深刻了解 JavaScript-new 做了什么
- 深刻了解 JavaScript-Object.create
- 深刻了解 JavaScript- 拷贝的机密
- 深刻了解 JavaScript- 原型
- 深刻了解 JavaScript- 继承
- 深刻了解 JavaScript-JavaScript 中的始皇
- 深刻了解 JavaScript-instanceof——找祖籍