函数声明
函数声明与闭包
参数
arguments
除了
In the case when iarg is less than the number of formal parameters for the function object, this property shares its value with the corresponding property of the activation object. This means that changing this property changes the corresponding property of the activation object and vice versa. The value sharing mechanism depends on the implementation.
换言之,假设一个函数的第一个参数是
(function (a) {
console.log(arguments[0] === a); // -> true
console.log(a); // -> 1
// 修改 arguments
arguments[0] = 10;
console.log(a); // -> 10
// 修改参数变量
a = 20;
console.log(arguments[0]); // -> 20
})(1, 2);
后面的事情你也知道了,
function foo(...args) {
// 这里 args 终于是真正的 Array 了!
}
Closure: 闭包
-
A closure is a function that has access to the parent scope, even after the scope has closed.
-
A closure is the combination of a function and the lexical environment within which that function was declared.
Closure
闭包本身是含有自由变量的代码块,在
function init() {
const name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
函数
function makeFunc() {
const name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
const myFunc = makeFunc();
myFunc();
运行这段代码的效果和之前的
避免闭包
在真实的开发中我们常常会使用闭包这一变量保留的特性来传递变量到异步函数中,不过闭包也往往会使程序出乎我们的控制,譬如在下面这个简单的循环中,我们本希望能够打印出
for (const i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i), 1000;
});
}
不过所有输入的
将异步获取值保留到新增的闭包中
我们可以考虑加一层闭包,将
function init3() {
const pAry = document.getElementsByTagName("p");
for( const i=0; i<pAry.length; i++ ) {
(function(arg){
pAry[i].onclick = function() {
alert(arg);
};
})(i);//调用时参数
}
}
或者在新增的闭包中将i
以局部变量形式传递给内部函数中
function init4() {
const pAry = document.getElementsByTagName("p");
for (const i = 0; i < pAry.length; i++) {
(function () {
const temp = i; //调用时局部变量
pAry[i].onclick = function () {
alert(temp);
};
})();
}
}
将变量值保留到作用域之外
在
function init() {
const pAry = document.getElementsByTagName("p");
for (const i = 0; i < pAry.length; i++) {
pAry[i].i = i;
pAry[i].onclick = function () {
alert(this.i);
};
}
}
也可以将变量i
保存在匿名函数本身
function init2() {
const pAry = document.getElementsByTagName("p");
for (const i = 0; i < pAry.length; i++) {
(pAry[i].onclick = function () {
alert(arguments.callee.i);
}).i = i;
}
}
const b = fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2
/*一开始fun(n=undefined,o=undefined)*/
/*
const b = fun(n=0,o=undefined); 执行后,相对应改变局部变量n和o.
执行console.log(undefined),返回:
const b={
fun:执行fun(n=1,o=0);console.log(0),返回:{
fun:执行fun(n=2,o=1);console.log(1),返回:{
fun:执行fun(n=3,o=2);console.log(2),返回:{
fun:function(3){
return fun(m,3)由于没有执行所以n不会赋值为undefined,o也不会赋值为3
}
}
}
}
}
*/
JavaScript Function
在function
关键字声明一个函数。(function() {})()
这种定义方式,完全等价于 (function() {}).call(window [ES5-strict: undefined)
这种方式。注意,在
Function Params: 函数参数
参数
Arguments: 自动注入的参数对象
在
(() => arguments)(1, 2, 3) // => uncaught reference error (function() { return arguments; })(1, 2, 3) // [1, 2, 3]
而在
((...args) => args)(1, 2, 3) // => [1,2,3]
默认参数与可选参数
当我们构造一个提供配置的对象,并且需要这个对象的属性携带默认值时,解构特性就派上用场了。举个例子,ajax
函数使用一个配置对象作为它的第二参数,我们可以这样重写函数定义:
jQuery.ajax = function (
url,
{
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... 更多配置
}
) {
// ... do stuff
};
同样,解构也可以应用在函数的多重返回值中,可以类似于其他语言中的元组的特性:
function returnMultipleValues() {
return [1, 2];
}
const [foo, bar] = returnMultipleValues();
单纯可选参数
上面所讲的,都是基于默认参数构造出的可选参数,而如果需要使用扁平化的可选参数,可以借鉴如下实现:
function example( err, optionalA, optionalB, callback ) {
// retrieve arguments as arrayvar args = new Array(arguments.length);
for(const i = 0; i < args.length; ++i) {
args[i] = arguments[i];
};
// first argument is the error object// shift() removes the first item from the// array and returns it
err = args.shift();
// if last argument is a function then its the callback function.// pop() removes the last item in the array// and returns itif (typeof args[args.length-1] === 'function') {
callback = args.pop();
}
// if args still holds items, these are// your optional items which you could// retrieve one by one like this:if (args.length > 0) optionalA = args.shift(); else optionalA = null;
if (args.length > 0) optionalB = args.shift(); else optionalB = null;
// continue as usual: check for errorsif (err) {
return callback && callback(err);
}
// for tutorial purposes, log the optional parametersconsole.log('optionalA:', optionalA);
console.log('optionalB:', optionalB);
console.log('callback:', callback);
/* do your thing */
}
// ES6 with shorter, more terse codefunction example(...args) {
// first argument is the error objectconst err = args.shift();
// if last argument is a function then its the callback functionconst callback = (typeof args[args.length-1] === 'function') ? args.pop() : null;
// if args still holds items, these are your optional items which you could retrieve one by one like this:const optionalA = (args.length > 0) ? args.shift() : null;
const optionalB = (args.length > 0) ? args.shift() : null;
// ... repeat for more itemsif (err && callback) return callback(err);
/* do your thing */
}
// invoke example function with and without optional argumentsexample(null, 'AA');
example(null, function (err) { /* do something */ });
example(null, 'AA', function (err) {});
example(null, 'AAAA', 'BBBB', function (err) {});
Required Parameters: 必要参数
在
function foo(mustBeProvided) {
if (arguments.length < 1) {
throw new Error();
}
if (! (0 in arguments)) {
throw new Error();
}
if (mustBeProvided === undefined) {
throw new Error();
}
···
}
而在
/**
* Called if a parameter is missing and
* the default value is evaluated.
*/
function mandatory() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = mandatory()) {
return mustBeProvided;
}
Interaction:
> foo()
Error: Missing parameter
> foo(123)
123
Anonymous:匿名函数
在myFunction = function(){}
的方式来声明一个匿名函数,而
// Expression bodies
const odds = evens.map((v) => v + 1);
const nums = evens.map((v, i) => v + i);
const pairs = evens.map((v) => ({ even: v, odd: v + 1 }));
// Statement bodies
nums.forEach((v) => {
if (v % 5 === 0) fives.push(v);
});
不过不同于
// Lexical this
const bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach((f) => console.log(this._name + " knows " + f));
},
};
Invoke( 函数调用)
常见的Function调用方式即是```FunctionName(args)```,也可以利用apply与call方式进行调用。
apply&call
Function.apply(obj,args) 方法能接收两个参数
obj:这个对象将代替
Function 类里this 对象args:这个是数组,它将作为参数传给
Function(args –>arguments )
- Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替
Function 类里this 对象params:这个是一个参数列表
在
function Point(x, y){
this.x = x;
this.y = y;
this.moveTo = function(x, y){
this.x = x;
this.y = y;
}
}
以Math.max为例:
Math.max(arr[0],arr[1]); Math.prototype.max.call(Math,arr[0],arr[1]); Math.prototype.max.apply(Math,arr);
const p1 = new Point(0, 0);
const p2 = {x: 0, y: 0};
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10]);
使用call/apply 来用Array 的方法处理Array-Like 对象
笔者之前一直有个疑问,就是从面向对象的思路上来说,调用对象的某个方法应该是用obj.Prototype.function.call(obj)
这种方式,为此笔者还特意在Reddit上问了个问题,在这里把大概的解释如下。这种情况更多的是出现在对于非
const items = { length: 2, 0: 'bob', 1: 'joe' };
console.log(items.forEach); // => undefined
[].forEach.call(items, x => console.log(x)) // => bob => joe
而在具体的编程中,这种
// arguments.length is an integer, this doesn't leak the arguments object itself
const args = new Array(arguments.length);
for(const i = 0; i < args.length; ++i) {
// i is always valid index in the arguments object, preventing another deoptimization.
args[i] = arguments[i];
}
console.clear();
const divs = document.getElementsByTagName("DIV");
console.dir(divs.__proto__); //HTMLCollection
//If it were an array, then proto would be "Array"
const arr = [1, 2, 3, 4];
console.dir(arr.__proto__); //Array[0]
//Yep.
//And the slice method won't even work with divs because the HTMLCollection prototype doesn't have that method
//divs.slice(); //Uncaught TypeError: divs.slice is not a function
//:(
//Now, through the magic of [].slice.call(), we can make divs an array...
divs = [].slice.call(divs);
console.dir(divs.__proto__); //Array[0]
//Bingo!
//Now...
const lastCouple = divs.slice(2);
console.dir(lastCouple); //Array[2]
//slice works now!
IIFE - Immediately Invoked Function Expression
Called as “Iffy” ( IIFE - immediately invoked function expression) is an anonymous function expression that is immediately invoked and has some important uses in Javascript.
(function() {
// Do something
}
)()
It is an anonymous function expression that is immediately invoked, and it has some particularly important uses in JavaScript.
The pair of parenthesis surrounding the anonymous function turns the anonymous function into a function expression or variable expression. So instead of a simple anonymous function in the global scope, or wherever it was defined, we now have an unnamed function expression.
Similarly, we can even create a named, immediately invoked function expression:
(someNamedFunction = function(msg) {
console.log(msg || "Nothing for today !!")
}) (); // Output --> Nothing for today !!
someNamedFunction("Javascript rocks !!"); // Output --> Javascript rocks !!
someNamedFunction(); // Output --> Nothing for today !!
不过let
关键字来创建块级别的作用域而不需要再使用这种
(function () {
const food = 'Meow Mix';
}());
console.log(food); // Reference Error
使用
{
let food = 'Meow Mix';
}
console.log(food); // Reference Error
不使用括号调用函数
new
关键字可以不带括号地调用函数:
function Greet() {
console.log("hello");
}
new Greet(); // parentheses are optional in this construct.
new
操作符的语法为:
new constructor[([arguments])]
toString
或者valueOf
方法
const greet = {
toString: function() {
return 'hello';
}
}
greet + ''; // 字符串连接会强制性转化到String类型,这样就隐性调用了toString
可以利用这种方式调用任意的函数
function func() {
console.log('hello');
}
const greet = {
toString: func
}
greet + '';
或者使用valueOf
function func() {
console.log('hello');
}
const greet = {
valueOf: func
}
+greet;
如果要使用
Function.prototype.valueOf = function() {
this.call(this);
// Optional improvement: avoid `NaN` issues when used in expressions.
return 0;
};
function greet() {
console.log('hello');
}
+greet;
function* func() {
console.log('hello');
}
const greet = {};
greet[Symbol.iterator] = func;
[...greet];
一般来说用迭代器的时候都会附带一个yield
语句,但是在这边希望调用某个函数的时候不一定要加上这个语句。上述代码中是最后一个语句调用了函数,同时也可以利用解构赋值来进行调用
[,] = greet;
或者使用
for ({} of greet);
(4 ) Getters
function func() {
console.log('hello');
}
Object.defineProperty(window, 'greet', { get: func });
greet;
也可以利用Object.assign
Object.assign(window, { get greet() {
console.log('hello');
}});
greet;
全局将 window
对象替换成一个你自定义的全局对象。
- Tagged Template Literals
function greet() {
console.log('hello');
}
greet``;
Lambda:Arrow Function
const o = {
traditionalFunc: function () {
// Normal function, bound as expected
console.log("traditionalFunc this === o?", this === o);
},
arrowFunc: () => {
// Arrow function, bound to scope where it's created
console.log("arrowFunc this === o?", this === o);
console.log("arrowFunc this === window?", this === window);
},
};
o.traditionalFunc();
// traditionalFunc this === o? true
o.arrowFunc();
// arrowFunc this === o? false
// arrowFunc this === window? true
上述代码中的
const asyncFunction = (param, callback) => {
window.setTimeout(() => {
callback(param);
}, 1);
};
// With a traditional function if we don't control
// the context then can we lose control of `this`.
const o = {
doSomething: function () {
// Here we pass `o` into the async function,
// expecting it back as `param`
asyncFunction(o, function (param) {
// We made a mistake of thinking `this` is
// the instance of `o`.
console.log("param === this?", param === this);
});
},
};
o.doSomething(); // param === this? false