JavaScript进阶教程(4)-函数内this指向解惑call(),apply(),bind()的区别

目录

1. 函数的定义方式

1.1 函数声明

1.2 函数表达式

1.3 函数声明与函数表达式的区别

1.4 构造函数Function(了解即可,一般不用)

2. 函数的调用方式

3. 函数内 this 的指向

4. call、apply、bind

4.1 call,apply

4.1.1 新的函数调用方式apply和call方法

4.1.2 apply和call可以改变this的指向

4.2 call,apply使用

4.3 bind

4.4 总结

5. 函数的其它成员(了解)

6. 高阶函数

6.1 作为参数

6.2 作为返回值

7. 总结


1. 函数的定义方式

定义函数的方式有三种:

  1. 函数声明
  2. 函数表达式
  3. new Function(一般不用)

1.1 函数声明

// 函数的声明
function fn() {
	console.log("我是JS中的一等公民-函数!!!哈哈");
}
fn();

1.2 函数表达式

函数表达式就是将一个匿名函数赋值给一个变量。函数表达式必须先声明,再调用。

// 函数表达式
var fn = function() {
	console.log("我是JS中的一等公民-函数!!!哈哈");
};
fn();

1.3 函数声明与函数表达式的区别

  1. 函数声明必须有名字。
  2. 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用。
  3. 函数表达式类似于变量赋值。
  4. 函数表达式可以没有名字,例如匿名函数。
  5. 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用。

下面是一个根据条件定义函数的例子:

if (true) {
  function f () {
    console.log(1)
  }
} else {
  function f () {
    console.log(2)
  }
}

以上代码执行结果在不同浏览器中结果不一致。我们可以使用函数表达式解决上面的问题:

var f

if (true) {
  f = function () {
    console.log(1)
  }
} else {
  f = function () {
    console.log(2)
  }
}

函数声明如果放在if-else的语句中,在IE8的浏览器中会出现问题,所以为了更好的兼容性我们以后最好用函数表达式,不用函数声明的方式。

1.4 构造函数Function(了解即可,一般不用)

在前面的学习中我们了解到函数也是对象。注意:函数是对象,对象不一定是函数,对象中有__proto__原型,函数中有prototype原型,如果一个东西里面有prototype,又有__proto__,说明它是函数,也是对象。

function F1() {}

console.dir(F1); // F1里面有prototype,又有__proto__,说明是函数,也是对象

console.dir(Math); // Math中有__proto__,但是没有prorotype,说明Math不是函数

对象都是由构造函数创建出来的,函数既然是对象,创建它的构造函数又是什么呢?事实上所有的函数实际上都是由Function构造函数创建出来的实例对象。

所以我们可以使用Function构造函数创建函数。

语法:new Function(arg1,arg2,arg3..,body);
arg是任意参数,字符串类型的。body是函数体。

// 所有的函数实际上都是Function的构造函数创建出来的实例对象
var f1 = new Function("num1", "num2", "return num1+num2");
console.log(f1(10, 20));
console.log(f1.__proto__ == Function.prototype);

// 所以,函数实际上也是对象
console.dir(f1);
console.dir(Function);

2. 函数的调用方式

  1. 普通函数
  2. 构造函数
  3. 对象方法
// 普通函数
function f1() {
	console.log("我是普通函数");
}
f1();

// 构造函数---通过new 来调用,创建对象
function F1() {
	console.log("我是构造函数");
}
var f = new F1();

// 对象的方法
function Person() {
	this.play = function() {
		console.log("我是对象中的方法");
	};
}
var per = new Person();
per.play();

3. 函数内 this 的指向

函数的调用方式决定了 this 指向的不同:

调用方式非严格模式备注
普通函数调用window严格模式下是 undefined
构造函数调用实例对象原型方法中 this 也是实例对象
对象方法调用该方法所属对象紧挨着的对象
事件绑定方法绑定事件对象
定时器函数window

// 普通函数
function f1() {
	console.log(this); // window
}
f1();

// 构造函数
function Person() {
	console.log(this); // Person
	// 对象的方法
	this.sayHi = function() {
		console.log(this); // Person
	};
}
// 原型中的方法
Person.prototype.eat = function() {
	console.log(this); // Person
};
var per = new Person();
console.log(per); // Person
per.sayHi();
per.eat();

// 定时器中的this
setInterval(function() {
	console.log(this); // window
}, 1000);

4. call、apply、bind

了解了函数 this 的指向之后,我们知道在一些情况下我们为了使用某种特定环境的 this 引用,需要采用一些特殊手段来处理,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。
然而实际上 JavaScript 内部已经专门为我们提供了一些函数方法,用来帮我们更优雅的处理函数内部 this 指向问题。这就是接下来我们要学习的 call、apply、bind 三个函数方法。

call()、apply()、bind()这三个方法都是是用来改变this的指向的。

4.1 call,apply

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。
apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。

注意:call() 和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。

call语法: 

fun.call(thisArg[, arg1[, arg2[, ...]]])

call参数:

  • thisArg

    • 在 fun 函数运行时指定的 this 值
    • 如果指定了 null 或者 undefined 则内部 this 指向 window
  • arg1, arg2, ...

    • 指定的参数列表

apply语法:

fun.apply(thisArg, [argsArray])

apply参数:

  • thisArg
  • argsArray

apply() 与 call() 相似,不同之处在于提供参数的方式。
apply() 使用参数数组而不是一组参数列表。例如:

fun.apply(this, ['eat', 'bananas'])

4.1.1 新的函数调用方式apply和call方法

function f1(x, y) {
	console.log("结果是:" + (x + y) + this);
	return "666";
}
f1(10, 20); // 函数的调用

console.log("========");

// apply和call方法也是函数的调用的方式
// 此时的f1实际上是当成对象来使用的,对象可以调用方法
// apply和call方法中如果没有传入参数,或者是传入的是null,那么调用该方法的函数对象中的this就是默认的window
f1.apply(null, [10, 20]);
f1.call(null, 10, 20);

// apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的
var result1 = f1.apply(null, [10, 20]);
var result2 = f1.call(null, 10, 20);
console.log(result1);
console.log(result2);

4.1.2 apply和call可以改变this的指向

// 通过apply和call改变this的指向
function Person(name, sex) {
	this.name = name;
	this.sex = sex;
}
//通过原型添加方法
Person.prototype.sayHi = function(x, y) {
	console.log("您好啊:" + this.name);
	return x + y;
};
var per = new Person("小三", "男");
var r1 = per.sayHi(10, 20);

console.log("==============");

function Student(name, age) {
	this.name = name;
	this.age = age;
}
var stu = new Student("小舞", 18);
var r2 = per.sayHi.apply(stu, [10, 20]);
var r3 = per.sayHi.call(stu, 10, 20);

console.log(r1);
console.log(r2);
console.log(r3);

4.2 call,apply使用

apply和call都可以改变this的指向。调用函数的时候,改变this的指向:

// 函数的调用,改变this的指向
function f1(x, y) {
	console.log((x + y) + ":===>" + this);
	return "函数的返回值";
}
//apply和call调用
var r1 = f1.apply(null, [1, 2]); // 此时f1中的this是window
console.log(r1);
var r2 = f1.call(null, 1, 2); // 此时f1中的this是window
console.log(r2);
console.log("=============>");
//改变this的指向
var obj = {
	sex: "男"
};
// 本来f1函数是window对象的,但是传入obj之后,f1的this此时就是obj对象
var r3 = f1.apply(obj, [1, 2]); //此时f1中的this是obj
console.log(r3);
var r4 = f1.call(obj, 1, 2); //此时f1中的this是obj
console.log(r4);

调用方法的时候,改变this的指向:

//方法改变this的指向
function Person(age) {
	this.age = age;
}
Person.prototype.sayHi = function(x, y) {
	console.log((x + y) + ":====>" + this.age); //当前实例对象
};

function Student(age) {
	this.age = age;
}
var per = new Person(10); // Person实例对象
var stu = new Student(100); // Student实例对象
// sayHi方法是per实例对象的
per.sayHi(10, 20);
per.sayHi.apply(stu, [10, 20]);
per.sayHi.call(stu, 10, 20);

总结:

apply的使用语法:
1 函数名字.apply(对象,[参数1,参数2,...]);
2 方法名字.apply(对象,[参数1,参数2,...]);
call的使用语法
1 函数名字.call(对象,参数1,参数2,...);
2 方法名字.call(对象,参数1,参数2,...);
它们的作用都是改变this的指向,不同的地方是参数传递的方式不一样。

如果想使用别的对象的方法,并且希望这个方法是当前对象的,就可以使用apply或者是call方法改变this的指向。

4.3 bind

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也可以接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。


bind方法是复制的意思,本质是复制一个新函数,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去。apply和call是调用的时候改变this指向,bind方法,是复制一份的时候,改变了this的指向。

语法:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, ...

    • 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值:

返回由指定的this值和初始化参数改造的原函数的拷贝。

示例1:

function Person(name) {
	this.name = name;
}
Person.prototype.play = function() {
	console.log(this + "====>" + this.name);
};

function Student(name) {
	this.name = name;
}
var per = new Person("人");
var stu = new Student("学生");
per.play();
// 复制了一个新的play方法
var ff = per.play.bind(stu);
ff();

示例2:

//通过对象,调用方法,产生随机数
function ShowRandom() {
	//1-10的随机数
	this.number = parseInt(Math.random() * 10 + 1);
}
//添加原型方法
ShowRandom.prototype.show = function() {
	//改变了定时器中的this的指向了
	window.setTimeout(function() {
		//本来应该是window, 现在是实例对象了
		//显示随机数
		console.log(this.number);
	}.bind(this), 1000);
};
//实例对象
var sr = new ShowRandom();
//调用方法,输出随机数字
sr.show();

4.4 总结

  • call 和 apply 特性一样

    • 都是用来调用函数,而且是立即调用
    • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向
    • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
    • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
    • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window
  • bind

    • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
    • 它和 call、apply 最大的区别是:bind 不会调用
    • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
      • 在 bind 的同时,以参数列表的形式进行传递
      • 在调用的时候,以参数列表的形式进行传递 
      • 那到底以 bind 的时候传递的参数为准呢?还是以调用的时候传递的参数为准呢?
      • 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部。

5. 函数的其它成员(了解)

  • arguments
    • 实参集合
  • caller
    • 函数的调用者
  • length
    • 函数定义的时候形参的个数
  • name
    • 函数的名字,name属性是只读的,不能修改
function fn(x, y, z) {
  console.log(fn.length) // => 形参的个数
  console.log(arguments) // 伪数组实参参数集合
  console.log(arguments.callee === fn) // 函数本身
  console.log(fn.caller) // 函数的调用者
  console.log(fn.name) // => 函数的名字
}

function f() {
  fn(10, 20, 30)
}

f()

6. 高阶函数

函数可以作为参数,也可以作为返回值。

6.1 作为参数

函数是可以作为参数使用,函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号。

function f1(fn) {
	console.log("我是函数f1");
	fn(); // fn是一个函数
}

//传入匿名函数
f1(function() {
	console.log("我是匿名函数");
});
// 传入命名函数
function f2() {
	console.log("我是函数f2");
}
f1(f2);

作为参数排序案例:

var arr = [1, 100, 20, 200, 40, 50, 120, 10];
//排序---函数作为参数使用,匿名函数作为sort方法的参数使用,此时的匿名函数中有两个参数,
arr.sort(function(obj1, obj2) {
	if (obj1 > obj2) {
		return -1;
	} else if (obj1 == obj2) {
		return 0;
	} else {
		return 1;
	}
});
console.log(arr);

6.2 作为返回值

function f1() {
	console.log("函数f1");
	return function() {
		console.log("我是函数,此时作为返回值使用");
	}

}

var ff = f1();
ff();

作为返回值排序案例: 

// 排序,每个文件都有名字,大小,时间,可以按照某个属性的值进行排序
// 三个文件,文件有名字,大小,创建时间
function File(name, size, time) {
	this.name = name; // 名字
	this.size = size; // 大小
	this.time = time; // 创建时间
}
var f1 = new File("jack.avi", "400M", "1999-12-12");
var f2 = new File("rose.avi", "600M", "2020-12-12");
var f3 = new File("albert.avi", "800M", "2010-12-12");
var arr = [f1, f2, f3];

function fn(attr) {
	// 函数作为返回值
	return function getSort(obj1, obj2) {
		if (obj1[attr] > obj2[attr]) {
			return 1;
		} else if (obj1[attr] == obj2[attr]) {
			return 0;
		} else {
			return -1;
		}
	}
}
console.log("按照名字排序:**********");
//  按照名字排序
var ff = fn("name");
// 函数作为参数
arr.sort(ff);
for (var i = 0; i < arr.length; i++) {
	console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
}

console.log("按照大小排序:**********");
// 按照大小排序
var ff = fn("size");
// 函数作为参数
arr.sort(ff);
for (var i = 0; i < arr.length; i++) {
	console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
}

console.log("按照创建时间排序:**********");
// 按照创建时间排序
var ff = fn("time");
// 函数作为参数
arr.sort(ff);
for (var i = 0; i < arr.length; i++) {
	console.log(arr[i].name + "====>" + arr[i].size + "===>" + arr[i].time);
}

7. 总结

函数中的this的指向:

  1. 普通的函数中this是window
  2. 构造函数中的this是实例对象
  3. 方法中的this是实例对象
  4. 原型中的方法中的this是实例对象
  5. 定时器中的this是window

函数是对象,对象不一定是函数,对象中有__proto__ 函数中有prototype。apply,call,bind方法是用来改变this指向的。apply和call是调用方法的时候改变this指向,bind方法是复制一个新的函数,改变this的指向。函数可以作为参数使用,也可以作为返回值使用。函数作为参数使用的时候,这个函数可以是匿名函数,也可以是命名函数。

转载至JavaScript进阶教程(4)-函数内this指向解惑call(),apply(),bind()的区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值