6.1函数简介
函数是一组语句的集合,它是一个独立运行的程序单元,本质上,它是一段子程序。函数是JavaScript的核心。
每个函数都有一个函数体,它是构成该函数的一组语句集合。
function sayHello(){
//这是函数体
console.log("Hello World11111");
console.log("Hello World22222");
console.log("Hello World33333");
}
//调用
sayHello();
6.2函数的传参
给函数调用传值的主要途径称为函数参数,函数参数可以给函数内部传值,参数是在函数调用后就不存在的变量。
//a和b称为形参
function avg(a, b) {
return (a + b) / 2;
}
//实际参数
avg(5,10);
6.3返回值
返回值,在函数体中,return 关键字会立即结束函数,并且返回一个特定值。
function getGreeting(){
return "Hello World";
}
let str = getGreeting();
console.log(str);
6.4参数列表
在其他编程语言中,一个函数的签名会包含它的参数列表。
比如,在C语言中,函数f()(没有参数)和函数f(x)(包含一个参数)是两个不同的函数。
但是JavaScript则对此不作区分,当有一个名为f的函数时,调用时可以给它0个、1个,甚至是10个参数,
不管调用时有多少参数,都始终调用了同一个函数。
function f(x){
return `in f:x=${x}`;
}
console.log(f());
console.log(f(1));
6.5引用调用
在JavaScript中,函数是一个对象,像其他对象一样,可以被传递和赋值。所以理解函数调用和引用之间的差别很重要。当在函数名后面添加小括号是,JavaScript就知道要调用它,然后执行函数体,并返回结果。如果没有小括号,仅仅是在引用这个函数,并没有调用它。
function getGreeting() {
return "Hello World";
}
getGreeting(); //调用函数
getGreeting //引用函数
const o = {};
o.f = getGreeting;
o.f();
console.log(o.f());
//把函数添加到数组里
const arr = [11, 12, 13];
arr[1] = getGreeting;
arr[1]();
console.log(arr[1]());
6.6解构参数
在解构赋值中,参数属性名必须是字符串标识符,而且如果传入的对象不存在与参数属性名匹配的属性,该属性将会接受一个undefined值。
//把一个对象解构到不通的变量中
function getSentence1({
subject,
verb,
object
}) {
return `${subject} ${verb} ${object}`;
}
const o = {
subject: "I",
verb: "love",
object: "JavaScript"
};
console.log(getSentence1(o));
//解构一个数组
const arr = ["I", "love", "JavaScript"];
function getSentence2([
subject,
verb,
object
]) {
return `${subject} ${verb} ${object}`;
}
console.log(getSentence2(arr));
6.6展开操作符(…)
注意,如果在函数定义中使用展开操作符,它必须是最后一个参数。
//展开操作符...匹配任何多出来的参数
function addPrefix(prefix, ...words) {
const prefixedWords = [];
for (let i = 0; i < words.length; i++) {
prefixedWords[i] = prefix + words[i];
}
return prefixedWords;
}
console.log(addPrefix("con", "verse", "vex"));
//如果在函数定义中使用展开操作符,它必须是最后一个参数。
6.7默认参数
ES6中的一个新特性是可以指定参数的默认值。通常,如果没有给参数传值,它的值将会是undefined。默认值则可以给这些参数指定其他的默认值。
function f(a, b = "default", c = 3) {
return `${a}-${b}-${c}`;
}
console.log(f(5,6,7)); //5-6-7
console.log(f(5,6)); //5-6-3
console.log(f(5)); //5-default-3
console.log(f()); //undefined-default-3
6.8值传递和引用传递
//基本类型的值传递
function f(x){
console.log("方法里的值:"+x);
x = 5;
console.log("方法里修改后值:"+x);
}
let x = 3;
console.log("原始值:"+3);
f(x);
//对象类型的,引用传递
function f(o) {
o.message = "f方法改变了值";
}
let o = {
message: "原始值"
}
console.log("o.message" + o.message);
f(o);
6.9函数和方法
当函数作为一个对象的属性时,通常被称为方法
const o = {
name: 'Wallace', //属性
bark: function() { //方法
return 'Woof';
}
}
//ES6为了方法引入了一个新的快捷语法。
const o1 = {
name: 'Wallace', //属性
bark() { //方法
return 'Woof';
}
}
6.10方法中的this关键字
this关键字通常与面向对象编程一起出现。第9章会详解更多用法。
this 关键字通常关联那些作为对象属性的函数。当方法被调用时,this 关键字的值就是被调用的对象
如何绑定this是由方法如何被调用所决定的,而非函数定义所决定
//ES6为了方法引入了一个新的快捷语法。
const o = {
name: 'Wallace', //属性
speak() { //方法
return `My name is ${this.name}`;
}
}
//当调用o.speak()的时候,this 关键字跟o进行了绑定
//this就是o 谁调用就是谁
console.log(o.speak());//My name is Wallace
//this是由方法如何被调用所决定的,而非函数定义所决定
const speak = o.speak;
console.log(speak === o.speak);//true
console.log(speak());//My name is
函数嵌套的this细节
const o = {
name: 'Julie',
greetBackwards: function() {
//name 反序列名字
function getReverseName() {
let nameBackwards = '';
for (let i = this.name.length - 1; i >= 0; i--) {
nameBackwards += this.name[i];
}
return nameBackwards;
}
return `反过来的名称是:${getReverseName()} `;
}
}
console.log(o.greetBackwards());
//this会被绑定一个到全局对象或者undefined
//一个常用的解决方案给this赋另一个变量: const self =this;
6.11箭头符号
ES6引入了一个新的语法叫作箭头符号。本质是一种语法糖
箭头函数允许使用以下3种方式来简化语法:
可以省略function这个单词。
如果函数只有一个参数,则可以省略小括号。
如果函数体是一个单独表达式,则可以省略大括号和返回语句。
const f = function() {
console.log("匿名函数");
}
//g名字优先级高
const g = function ff() {
}
//箭头函数大多是匿名函数
const f1 = function() {
return "Hello!";
}
//简写
const f1 = () => "Hello";
const f2 = function(name) {
return `Hello,${name}`;
}
//简写
const f2 = name => `Hello,${name}`;
const f3 = function(a, b) {
return a + b;
}
//简写
const f3 = (a, b) => a + b;
箭头函数跟普通函数之间的主要区别是:this。
普通函数的this是,指向调用的那个对象,谁调用就是谁
箭头函数不会创建自己的this,箭头函数的this,始终指向父级上下文。
const o = {
name: 'Julie',
greetBackwards: function() {
//name 反序列名字
const getReverseName=()=> {
let nameBackwards = '';
for (let i = this.name.length - 1; i >= 0; i--) {
nameBackwards += this.name[i];
}
return nameBackwards;
}
return `反过来的名称是:${getReverseName()} `;
}
}
console.log(o.greetBackwards());
6.12调用call,请求apply和绑定bind
指定this的绑定对象
call方法在所有函数上都可用,它允许使用指定的this来调用函数。
const bruce = {
name: "Bruce"
};
const madeline = {
name: "Madeline"
};
function greet() {
return `Hello,I am ${this.name}`;
}
console.log(greet());; // this没有绑定任何值
console.log(greet.call(bruce)); //this 绑定了 bruce
console.log(greet.call(madeline)); //this 绑定了 madeline
可以看到call方法允许在调用函数时给this绑定一个对象,就好像有一个对象在做这件事。
call方法的第一个参数是想要的绑定的值,剩下的参数则变成了要调用的函数的参数
const bruce = {
name: "Bruce"
};
const madeline = {
name: "Madeline"
};
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
update.call(bruce, 1949, 'singer');
//bruce:{name:Bruce,birthYear:1949,occupation:singer}
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
update.call(madeline, 1942, 'actress');
//madeline:{name:Madeline,birthYear:1942,occupation:actress}
console.log(`madeline:{name:${madeline.name},birthYear:${madeline.birthYear},occupation:${madeline.occupation}}`);
除了处理函数参数的方式不同,apply 与call基本是一致的。call 会像一般的函数一样直接获取参数。apply则以数组的方式获取参数
apply 比较适合用于将数组作为函数参数的场景。
update.apply(bruce, [1949, 'singer']);
//bruce:{name:Bruce,birthYear:1949,occupation:singer}
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
update.apply(madeline, [1942, 'actress']);
//madeline:{name:Madeline,birthYear:1942,occupation:actress}
console.log(`madeline:{name:${madeline.name},birthYear:${madeline.birthYear},occupation:${madeline.occupation}}`);
apply可以改变传递给函数参数的形式
如果参数本来就存在一个数组中,那就用 apply,如果参数比较散乱相互之间没什么关联,就用 call
//apply可以改变传递给函数参数的形式
const arr = [2, 3, -5, 15, 7];
console.log(Math.min(2, 3, 5, -5, 15, 7));
console.log(Math.min.apply(null, arr));
// ES6中的展开运算符((...),可以实现与apply同样的功能。
const newBruce = [1940, "martial artist"];
update.call(bruce, ...newBruce);
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
console.log(Math.min(...arr));
bind方法可以给一个函数永久地邦定this值。
bind方法返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行。
const updateBruce = update.bind(bruce);
updateBruce(1904, "actor");
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
updateBruce.call(madeline, 1274, "king");
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
调用bind方法时也可以传参数,这跟创建一个有固定参数的新函数效果一样。
比如,希望update函数总是把bruce 的出生年份设为1949,但却允许修改他的职业,可以这样做
const updateBruce1949 = update.bind(bruce, 1949);
updateBruce1949("singer,songwriter");
console.log(`bruce:{name:${bruce.name},birthYear:${bruce.birthYear},occupation:${bruce.occupation}}`);
思考为啥要改变this绑定值?
const obj1 = {
name: "rxy",
speak() {
console.log("姓名:" + this.name);
}
}
obj1.speak();
const obj2 = {
name: "tom",
}
obj1.speak.call(obj2);
function Vehicle() {
this.type = "汽车";
}
function Car(make, model) {
//arguments,它是js中函数内置的一个对象,而执行函数方法的实参中值都存储在arguments中
//通过下标/索引来获取每个参数的值
Vehicle.apply(this, arguments);
this.make = make;
this.model = model;
}
var car = new Car("Tesla","ModelS");
alert(car.type); // 汽车
alert(car.make); // Tesla
alert(car.model); // ModelS