引用类型之-Function
声明
var result = add(1,2,3,4,5,6,7,8,9); console.log(result); function add() { var aArgs = [].slice.call(arguments); return aArgs.reduce((a,b) => a+b); } console.log( _add(1,2,3,4) ); var _add = function () { var aArgs = [].slice.call(arguments); return aArgs.reduce((a,b) => a+b); }
|
return&返回值
如果不显式返回值,那么这个函数的返回值默认为undefined
function cal (num1,num2, calType) { switch (calType) { case 'add': return num1+num2; break; case 'minus': return num1-num2; break; default: return; break; } } console.log( cal(1,2,'times') );
|
任何时候一旦function内部遇到return,函数立即退出。
function fn( command ) { if(command === 'stop'){ return; } return arguments.callee.name + ' is running...'; } console.log( fn() ); console.log( fn('stop') )
|
Arguments
记住一点,形参和arguments对象的元素是同步的,且它们的内存空间是独立的,如果没有传入参数,arguments如同定义了却没有初始化一样,值是undefined.
关于函数内部的argumens对象我们有两项需要知道:
- arguments.callee: 指针,指向拥有这个arguments对象的函数(本身),作用是可以对函数进行解耦
function factorial(num) { if( num <=1 ) { return 1; } else { return num * factorial(--num); } } function goodFactorial(num) { if( num <=1 ) { return 1; } else { return num * arguments.callee(--num); } }
|
- arguments.callee.caller: 保存着调用当前函数的函数的引用,如果在全局作用域里调用当前函数,它的值为null.
function outer(){ inner(); } function inner() { console.log(arguments.callee.caller); } outer(); inner(); */ }
|
length
function本质是对象,它也具有length属性,它的数值是由function声明时的形参个数决定的.
function fn(a,b,c) { return a+b+c; } console.log(fn.length);
|
注意这种特殊情况,rest参数下,此参数不计入length.
function _fn(firstArgs, ...args) { return [].slice.call(arguments); } console.log(_fn.length);
|
我们还可以利用这个属性进行参数检测
Function.prototype.before = function (beforeCheckFn) { var _this = this; return function(){ if(beforeCheckFn.apply(this,arguments)){ return _this.apply(this, arguments); } } } function printTwoNum (num1, num2) { [].slice.call(arguments).forEach((a)=>(console.log(a))); } function checkArgs (num1,num2) { if( arguments.length !== arguments.callee.length) { throw new Error('The amounts of arguments is not allowed.'); return false; } return true; } let newPrint = printTwoNum.before(checkArgs); newPrint(1,2); newPrint(1,2,3);
|
没有重载
function add (num1) { return num1 + 100; } function add (num1) { return num2 + 200; }
|
这里涉及到一个惰性函数的问题,如果在当前开发环境下我们需要进行环境监测从而判断函数如何执行(多见于兼容性监测),那么在执行环境稳定不变的前提下,我们不必每次都进行判断,我们可以在第一次判断时就给函数定性,从而在之后的调用中都直接执行适合当前环境的函数。
var addEvent = function(ele, type, handler) { if(window.addEventListener) { addEvent = function (ele, type, handler) { ele.addEventListener(type, handler, false); }; }else if (window.attachEvent) { addEvent = function (ele, type, handler) { ele.attachEvent('on' + type, handler); }; } addEvent(elem, type, handler); }
|
this
函数内部的this指向函数的执行上下文,通俗的说就是函数被谁调用就指向谁。
// var obj = { a: 1, getA: function () { console.log(this === obj, this.a); } } obj.getA();
|
window.name = 'globalName'; function getName() { console.log(this.name); } getName(); var myObject = { name: 'kylewh', getMyName: function () { return this.name; } } var getMyName = myObject.getMyName; console.log(getMyName()); function func() { "use strict" console.log(this); } func();
|
function Construc() { this.name = 'kylewh'; } var ooobj = new Construc(); console.log(ooobj.name); function _Construc() { this.name = 'kylewh'; return { name: 'wenhao' } } var oooobj = new _Construc(); console.log(oooobj.name);
|
我们还可以在函数内部使用return this
来达成链式调用的效果.
Function.prototype.addMethod = function (name, fn) { this[name] = fn; return this; } var method = function () {}; method .addMethod('checkName', function () { console.log('验证名字'); return this; }).addMethod('checkEmail', function () { console.log('验证Email'); return this; }).addMethod('checkPhone', function () { console.log('验证手机'); return this; }); console.dir(method); method.checkName().checkEmail().checkPhone();
|
call & apply
这两个函数的作用本质上都是改变函数的执行上下文,并传入执行参数,而apply是可以支持以数组的的形式传入参数的。
window.color = "red"; var o = { color: 'blue' }; function sayColor() { alert(this.color); } sayColor(); sayColor.call(this); sayColor.call(window); sayColor.call(o); function _add(num1 , num2) { console.log ( num1 + num2 ); } var num = [10,20]; _add.apply(null, num);
|
bind
也是用来改变函数的执行上下文,当时返回的是绑定传入上下文之后的函数。
var test = { name: 'kylewhtest' }; var fn2 = function (num1, num2, num3) { console.log(this.name + num1 + num2 + num3); }.bind(test); fn2(1, 2, 3); var fn2 = function (num1, num2, num3) { console.log(this.name + num1 + num2 + num3); }.bind(test,1,2); fn2(3);
|
一些例子
var john = { firstName: "John" } function func() { alert(this.firstName + ": hi!") } john.sayHi = func john.sayHi()
|
func() function func() { alert(this) }
|
document.addEventListener('click', function(e){ console.log(this); setTimeout(function(){ console.log(this); }, 200); }, false);
|
var john = { firstName: "John" } function func() { alert( this.firstName ) } func.call(john)
|
var module= { bind: function(){ $btn.on('click', function(){ console.log(this) this.showMsg(); }) }, showMsg: function(){ console.log('hi'); } }
|
修改版:
var module= { bind: function(){ let _this = this; $btn.on('click', function(){ console.log(this) _this.showMsg(); }) }, showMsg: function(){ console.log('You clicked the button!'); } } module.bind();
|
综合运用
根据前面的call和apply,我们可以先模拟一个简单版本的bind,不考虑部分参数传入的问题:
Function.prototype.bind = function (context) { var _this = this; return function () { return _this.apply(context, arguments); } }
|
再加上前后参数拼接在一起,完整版:
Function.prototype.bind = function () { var _this = this; var context = [].shift.call(arguments); var args = [].slice.call(arguments); return function(){ var newArgs = [].concat.call( args, [].slice.call(arguments) ); return _this.apply(context, newArgs); } } var test = { name: 'kylewhtest' }; var fn2 = function (num1, num2, num3) { console.log(this.name + num1 + num2 + num3); }.bind(test,1,2); fn2(3);
|
关于参数
ES6新特性:可以在函数声明时传入默认参数
function sayName(name = 'kyle') { console.log(`My name is ${name}`); } sayName(); function sayName(name = 'kyle', callback = function(){}) { console.log(`My name is ${name}`); callback(); }
|
又是烦人的this
先看下面的代码:
var fn = function(){ console.log(this.a); } function fn1 (){ console.log(this.a); } var obj = {a:2, fn:fn, fn1:fn1}, a = 'Surprise!! >_<'; obj.fn(); obj.fn1(); var bar = obj.fn; bar(); function doFn(fn){ fn(); } doFn(obj.fn); setTimeout(obj.fn1, 1000); setTimeout(function(){ obj.fn1(); },2000)
|
出现这种问题的原因本质是bar()
与obj.fn()
和 obj.fn1
与obj.fn1()
的区别:
请看着这条语句: var bar = obj.fn
, 现在开始你问我答环节。
- 问: 等号的出现意味着什么!?
- 答: 传递引用!
- 问: Nice! 那引用的对象是谁!?
- 答: fn 这个函数
- 问: 好,那么执行的方式是怎样的!?
- 答: 独立调用!
- 问: 独立调用时,存在对象上下文吗!?
- 答: 没有….额…等等…. window算吗?
- 问: Excelletn baby! 就是window. 那this指向谁?
- 答: window嘛!!
- 问: 再看看延迟函数里的回调传入方式,传入obj.fn1时是在干什么?
- 答: 传递引用!!
- 问: 那匿名函数里的obj.fn1()是在干什么?
- 答: 执行函数啊!
- 问: 有没有对象上下文!?this指向谁!?
- 答: 哈哈哈哈哈我懂了,this指向obj
这就是我们为什么需要注意函数表达式和函数声明的原因,因为在有些时候可能因为你使用函数表达式的方式对函数引用进行了传递,执行后从而得到你意想不到的效果。 注意跑!