台州网站排名公司,亿网网络科技有限公司,做拍卖网站需要多少钱,网页设计的工作流程目录 概述1. 箭头函数2. 函数名 #xff1a;指向函数的指针3. 理解参数3.1 arguments 对象的作用3.2 arguments 的注意点3.3 箭头函数中的参数 4. 没有重载5. 默认参数值5.1 ES 6 支持显示定义默认参数5.2 传 undefined 等于没有传值5.3 arguments 不反映参数默认值5.4 默认值… 目录 概述1. 箭头函数2. 函数名 指向函数的指针3. 理解参数3.1 arguments 对象的作用3.2 arguments 的注意点3.3 箭头函数中的参数 4. 没有重载5. 默认参数值5.1 ES 6 支持显示定义默认参数5.2 传 undefined 等于没有传值5.3 arguments 不反映参数默认值5.4 默认值可以是函数返回值5.4 箭头函数也可以使用默认参数值5.5 默认参数作用域5.6 暂时性死区 函数一共分为三篇文章分别为
《js 深入理解函数一函数的本质》《js 深入理解函数二扩展操作符、函数的内部对象、属性和方法》《js 深入理解函数三巧妙使用》
概述 js 中的函数实际上是对象。每个函数都是 Function类型的实例而 Function 也有属性和方法跟其他引用类型一样。因为函数是对象所以函数名就是指向函数对象的指针而且不一定与函数本身紧密绑定。函数通常以函数声明的方式定义比如
function sum (num1, num2) {return num1 num2;
}注意函数定义最后没有加分号。 另一种定义函数的语法是函数表达式。函数表达式与函数声明几乎是等价的
let sum function(num1, num2) {return num1 num2;
};这里代码定义了一个变量 sum 并将其初始化为一个函数。注意 function 关键字后面没有名称因为不需要。这个函数可以通过变量 sum 来引用。 注意这里的函数末尾是有分号的与任何变量初始化语句一样。 还有一种定义函数的方式与函数表达式很像叫作“箭头函数”arrow function如下所示
let sum (num1, num2) {return num1 num2;
};最后一种定义函数的方式是使用 Function 构造函数。这个构造函数接收任意多个字符串参数最后一个参数始终会被当成函数体而之前的参数都是新函数的参数。来看下面的例子
let sum new Function(num1, num2, return num1 num2); // 不推荐我们不推荐使用这种语法来定义函数因为这段代码会被解释两次第一次是将它当作常规 ECMAScript 代码第二次是解释传给构造函数的字符串。这显然会影响性能。不过把函数想象为对象把函数名想象为指针是很重要的。而上面这种语法很好地诠释了这些概念。 这几种实例化函数对象的方式之间存在微妙但重要的差别本章后面会讨论。无论如何通过其中任何一种方式都可以创建函数。 1. 箭头函数 ECMAScript 6 新增了使用箭头 语法定义函数表达式的能力。很大程度上箭头函数实例化的函数对象与正式的函数表达式创建的函数对象行为是相同的。任何可以使用函数表达式的地方都可以使用箭头函数
let arrowSum (a, b) {return a b;
};
let functionExpressionSum function(a, b) {return a b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13箭头函数简洁的语法非常适合嵌入函数的场景
let ints [1, 2, 3];
console.log(ints.map(function(i) { return i 1; })); // [2, 3, 4]
console.log(ints.map((i) { return i 1 })); // [2, 3, 4] 如果只有一个参数那也可以不用括号。只有没有参数或者多个参数的情况下才需要使用括号
// 以下两种写法都有效
let double (x) { return 2 * x; };
let triple x { return 3 * x; };
// 没有参数需要括号
let getRandom () { return Math.random(); };
// 多个参数需要括号
let sum (a, b) { return a b; };
// 无效的写法
let multiply a, b { return a * b; };箭头函数也可以不用大括号但这样会改变函数的行为。使用大括号就说明包含“函数体”可以在一个函数中包含多条语句跟常规的函数一样。如果不使用大括号那么箭头后面就只能有一行代码比如一个赋值操作或者一个表达式。而且省略大括号会隐式返回这行代码的值
// 以下两种写法都有效而且返回相应的值
let double (x) { return 2 * x; };
let triple (x) 3 * x;
// 可以赋值
let value {};
let setName (x) x.name Matt;
setName(value);
console.log(value.name); // Matt
// 无效的写法
let multiply (a, b) return a * b;箭头函数虽然语法简洁但也有很多场合不适用。箭头函数不能使用 arguments 、 super 和 new.target 也不能用作构造函数。此外箭头函数也没有 prototype 属性。
2. 函数名 指向函数的指针 因为函数名就是指向函数的指针所以它们跟其他包含对象指针的变量具有相同的行为。这意味着一个函数可以有多个名称如下所示
function sum(num1, num2) {return num1 num2;
}
console.log(sum(10, 10)); // 20
let anotherSum sum;
console.log(anotherSum(10, 10)); // 20
sum null; //切断了sum与函数之间的关联
console.log(anotherSum(10, 10)); // 20
console.log(sum(10, 10)); //报错 Uncaught TypeError: sum is not a function以上代码定义了一个名为 sum() 的函数用于求两个数之和。然后又声明了一个变量 anotherSum 并将它的值设置为等于 sum 。注意使用不带括号的函数名会访问函数指针而不会执行函数。此时anotherSum 和 sum 都指向同一个函数。调用 anotherSum() 也可以返回结果。把 sum 设置为 null之后就切断了它与函数之间的关联。而 anotherSum() 还是可以照常调用没有问题。 ECMAScript 6 的所有函数对象都会暴露一个只读的 name 属性其中包含关于函数的信息。多数情况下这个属性中保存的就是一个函数标识符或者说是一个字符串化的变量名。即使函数没有名称也会如实显示成空字符串。如果它是使用 Function 构造函数创建的则会标识成 “anonymous”
function foo() {}
let bar function() {};
let baz () {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() {}).name); //空字符串
console.log((new Function()).name); // anonymous如果函数是一个获取函数、设置函数或者使用 bind() 实例化那么标识符前面会加上一个前缀
function foo() {}
console.log(foo.bind(null).name); // bound foo 这里带了一个 前缀 bound//bind() 用于将函数内的this指向目标对象bind的第一个参数,这里将目标对象置null了
let dog {years: 1,get age() {return this.years;},set age(newAge) {this.years newAge;}
}
let propertyDescriptor Object.getOwnPropertyDescriptor(dog, age);
console.log(propertyDescriptor.get.name); // get age ,带了前缀 get
console.log(propertyDescriptor.set.name); // set age ,带了前缀 set
3. 理解参数
3.1 arguments 对象的作用 ECMAScript 函数的参数跟大多数其他语言不同。ECMAScript 函数既不关心传入的参数个数也不关心这些参数的数据类型。定义函数时要接收两个参数并不意味着调用时就传两个参数。你可以传一个、三个甚至一个也不传解释器都不会报错。 之所以会这样主要是因为 ECMAScript 函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组但函数并不关心这个数组中包含什么。如果数组中什么也没有那没问题如果数组的元素超出了要求那也没问题。事实上在使用 function 关键字定义非箭头函数时可以在函数内部访问 arguments 对象从中取得传进来的每个参数值。 arguments 对象是一个类数组对象但不是 Array 的实例因此可以使用中括号语法访问其中的元素第一个参数是 arguments[0] 第二个参数是 arguments[1] 。而要确定传进来多少个参数可以访问 arguments.length 属性。 在下面的例子中 sayHi() 函数的第一个参数叫 name
function sayHi(name, message) {console.log(Hello name , message);
}可以通过 arguments[0] 取得相同的参数值。因此把函数重写成不声明参数也可以
function sayHi() {console.log(Hello arguments[0] , arguments[1]);
}在重写后的代码中没有命名参数。 name 和 message 参数都不见了但函数照样可以调用。这就表明ECMAScript 函数的参数只是为了方便才写出来的并不是必须写出来的。与其他语言不同在 ECMAScript 中的命名参数不会创建让之后的调用必须匹配的函数签名。这是因为根本不存在验证命名参数的机制。 也可以通过 arguments 对象的 length 属性检查传入的参数个数。下面的例子展示了在每调用一个函数时都会打印出传入的参数个数
function howManyArgs() {console.log(arguments.length);
}
howManyArgs(string, 45); // 2
howManyArgs(); // 0
howManyArgs(12); // 1这个例子分别打印出 2、0 和 1按顺序。既然如此那么开发者可以想传多少参数就传多少参数。比如
function doAdd() {if (arguments.length 1) {console.log(arguments[0] 10);} else if (arguments.length 2) {console.log(arguments[0] arguments[1]);}
}
doAdd(10); // 20
doAdd(30, 20); // 50这个函数 doAdd() 在只传一个参数时会加 10在传两个参数时会将它们相加然后返回。因此 doAdd(10) 返回 20而 doAdd(30,20) 返回 50。虽然不像真正的函数重载那么明确但这已经足以弥补 ECMAScript 在这方面的缺失了。
3.2 arguments 的注意点 还有一个必须理解的重要方面那就是 arguments 对象可以跟命名参数一起使用比如
function doAdd(num1, num2) {if (arguments.length 1) {console.log(num1 10);} else if (arguments.length 2) {console.log(arguments[0] num2);}
}在这个 doAdd() 函数中同时使用了两个命名参数和 arguments 对象。命名参数 num1 保存着与arugments[0] 一样的值因此使用谁都无所谓。同样 num2 也保存着跟 arguments[1] 一样的值。 arguments 对象的另一个有意思的地方就是它的值始终会与对应的命名参数同步。来看下面的例子
function doAdd(num1, num2) {arguments[1] 10;console.log(arguments[0] num2);
}
doAdd(100,100); //110
doAdd(100); //NaN ,第2个参数没有赋值即使arguments[1]赋值了num2也没有获取到值因为此时num2并不存在这个 doAdd() 函数把第二个参数的值重写为 10。因为 arguments 对象的值会自动同步到对应的命名参数所以修改 arguments[1] 也会修改 num2 的值因此两者的值都是 10。但这并不意味着它们都访问同一个内存地址它们在内存中还是分开的只不过会保持同步而已。另外还要记住一点如果只传了一个参数然后把 arguments[1] 设置为某个值那么这个值并不会反映到第二个命名参数(上例中第6行就是这种情况)。这是因为 arguments 对象的长度是根据传入的参数个数而非定义函数时给出的命名参数个数确定的。 对于命名参数而言如果调用函数时没有传这个参数那么它的值就是 undefined 。这就类似于定义了变量而没有初始化。比如如果只给 doAdd() 传了一个参数那么 num2 的值就是 undefined 。 严格模式下 arguments 会有一些变化。首先像前面那样给 arguments[1] 赋值不会再影响 num2的值。就算把 arguments[1] 设置为 10 num2 的值仍然还是传入的值。其次在函数中尝试重写arguments 对象会导致语法错误。代码也不会执行。
3.3 箭头函数中的参数 如果函数是使用箭头语法定义的那么传给函数的参数将不能使用 arguments 关键字访问而只能通过定义的命名参数访问。
function foo() {console.log(arguments[0]);
}
foo(5); // 5
let bar () {console.log(arguments[0]);
};
bar(5); // ReferenceError: arguments is not defined虽然箭头函数中没有 arguments 对象但可以在包装函数中把它提供给箭头函数
function foo() {let bar () {console.log(arguments[0]); // 5};bar();
}
foo(5);注意 ECMAScript 中的所有参数都按值传递的。不可能按引用传递参数。如果把对象作为参数传递那么传递的值就是这个对象的引用。 4. 没有重载 ECMAScript 函数不能像传统编程那样重载。在其他语言比如 Java 中一个函数可以有两个定义只要签名接收参数的类型和数量不同就行。如前所述ECMAScript 函数没有签名因为参数是由包含零个或多个值的数组表示的。没有函数签名自然也就没有重载。 如果在 ECMAScript 中定义了两个同名函数则后定义的会覆盖先定义的。来看下面的例子
function addSomeNumber(num) {return num 100;
}
function addSomeNumber(num) {return num 200;
}
let result addSomeNumber(100); // 300这里函数 addSomeNumber() 被定义了两次。第一个版本给参数加 100第二个版本加 200。最后一行调用这个函数时返回了 300因为第二个定义覆盖了第一个定义。 前面也提到过可以通过检查参数的类型和数量然后分别执行不同的逻辑来模拟函数重载。 把函数名当成指针也有助于理解为什么 ECMAScript 没有函数重载。在前面的例子中定义两个同名的函数显然会导致后定义的重写先定义的。而那个例子几乎跟下面这个是一样的
let addSomeNumber function(num) {return num 100;
};
addSomeNumber function(num) {return num 200;
};
let result addSomeNumber(100); // 300看这段代码应该更容易理解发生了什么。在创建第二个函数时变量 addSomeNumber 被重写成保存第二个函数对象了。
5. 默认参数值
5.1 ES 6 支持显示定义默认参数 在 ECMAScript 5.1 及以前实现默认参数的一种常用方式就是检测某个参数是否等于 undefined 如果是则意味着没有传这个参数那就给它赋一个值
function makeKing(name) {name (typeof name ! undefined) ? name : Henry; // 没有赋值则给一个默认值return King ${name} VIII;
}
console.log(makeKing()); // King Henry VIII
console.log(makeKing(Louis)); // King Louis VIIIECMAScript 6 之后就不用这么麻烦了因为它支持显式定义默认参数了。下面就是与前面代码等价的 ES6 写法只要在函数定义中的参数后面用 就可以为参数赋一个默认值
function makeKing(name Henry) {return King ${name} VIII;
}
console.log(makeKing(Louis)); // King Louis VIII
console.log(makeKing()); // King Henry VIII5.2 传 undefined 等于没有传值 给参数传 undefined 相当于没有传值不过这样可以利用多个独立的默认值
function makeKing(name Henry, numerals VIII) {return King ${name} ${numerals};
}
console.log(makeKing()); // King Henry VIII
console.log(makeKing(Louis)); // King Louis VIII
console.log(makeKing(undefined, VI)); // King Henry VI5.3 arguments 不反映参数默认值 在使用默认参数时 arguments 对象的值不反映参数的默认值只反映传给函数的参数。当然跟 ES5 严格模式一样修改命名参数也不会影响 arguments 对象它始终以调用函数时传入的值为准
function makeKing(name Henry) {name Louis;return King ${arguments[0]};
}
console.log(makeKing()); // King undefined
console.log(makeKing(Louis)); // King Louis5.4 默认值可以是函数返回值 默认参数值并不限于原始值或对象类型也可以使用调用函数返回的值
let romanNumerals [I, II, III, IV, V, VI];
let ordinality 0;
function getNumerals() {// 每次调用后递增return romanNumerals[ordinality];
}
function makeKing(name Henry, numerals getNumerals()) {return King ${name} ${numerals};
}
console.log(makeKing()); // King Henry I
console.log(makeKing(Louis, XVI)); // King Louis XVI
console.log(makeKing()); // King Henry II
console.log(makeKing()); // King Henry III函数的默认参数只有在函数被调用时才会求值不会在函数定义时求值。而且计算默认值的函数只有在调用函数但未传相应参数时才会被调用。
5.4 箭头函数也可以使用默认参数值 箭头函数同样也可以这样使用默认参数只不过在只有一个参数时就必须使用括号而不能省略了
let makeKing (name Henry) King ${name};
console.log(makeKing()); // King Henry5.5 默认参数作用域 因为在求值默认参数时可以定义对象也可以动态调用函数所以函数参数肯定是在某个作用域中求值的。 给多个参数定义默认值实际上跟使用 let 关键字顺序声明变量一样。来看下面的例子
function makeKing(name Henry, numerals VIII) {return King ${name} ${numerals};
}
console.log(makeKing()); // King Henry VIII这里的默认参数会按照定义它们的顺序依次被初始化。可以依照如下示例想象一下这个过程
function makeKing() {let name Henry;let numerals VIII;return King ${name} ${numerals};
}5.6 暂时性死区 因为参数是按顺序初始化的所以后定义默认值的参数可以引用先定义的参数。看下面这个例子
function makeKing(name Henry, numerals name) {return King ${name} ${numerals};
}
console.log(makeKing()); // King Henry Henry参数初始化顺序遵循“暂时性死区”规则即前面定义的参数不能引用后面定义的。像这样就会抛出错误
// 调用时不传第一个参数会报错
function makeKing(name numerals, numerals VIII) {return King ${name} ${numerals};
}参数也存在于自己的作用域中它们不能引用函数体的作用域
// 调用时不传第二个参数会报错
function makeKing(name Henry, numerals defaultNumeral) {let defaultNumeral VIII;return King ${name} ${numerals};
}