文章类型:学习笔记
1.bind()的使用
😏😏😏😏😏
1
2
3
4
5
6
7
8
9
|
let obj = {
age: 18
}
function getInfo(name, job) {
console.log(name);
console.log(job);
console.log(this.age);
}
getInfo.bind(obj, '星空海绵')('Software Engineer');
|
bind在第一个参数之后的参数,可以直接从bind()传,也可以从bind()返回的函数上传,如上代码。
2.bind()参数
🤔🤔🤔🤔🤔
(1)thisArg
bind的第一个参数是bind被函数调用时作为this参数传给调用函数。如果bind()参数列表为空,或者thisArg为null或undefined, 执行作用域的this 就是新函数的this。如果使用 new操作符 操作绑定函数,则会 忽略这个thisArg参数 。当使用bind在setTimeout中创建一个函数时,作为thisArg传递的任何原始值都会被转换为object。-MDN
- 1.bind的第一个参数是bind被函数调用时作为this参数传给调用函数
1
2
3
4
5
6
7
|
let obj = {
age: 18
}
function getInfo() {
console.log(this); // {age: 18}
}
getInfo.bind(obj)();
|
此时调用函数 getInfo 中的 this 就是 bind() 中传入的 obj
- 2.bind()参数列表为空,或者thisArg为null或undefined,执行作用域的this就是新函数的this
1
2
3
4
5
6
7
8
9
|
let obj = {
age: 18
}
function getInfo() {
console.log(this); // Window {}
}
getInfo.bind()();
getInfo.bind(null)();
getInfo.bind(undefined)();
|
此时bind的参数为 空 或 null 或 undefined,因为实际上 getInfo 是全局对象调用的,在这里也就是 window 对象,所以此时this就是指向 window 对象的。
- 3.如果使用new操作符操作绑定函数,则会忽略这个thisArg参数
1
2
3
4
5
6
7
8
|
let obj = {
age: 18
}
function getInfo() {
console.log(this); // getInfo {}
}
let a = getInfo.bind(obj)
let b = new a();
|
此时不管调用的时候传不传参数 obj,getInfo 中的 this 都是指向创建对象 b 的,这里就涉及到 new 构造函数创建对象的过程了,建议精读 MDN 这部分。
- 4.当使用bind在setTimeout中创建一个函数时,作为thisArg传递的任何原始值都会被转换为object。
1
2
3
4
5
6
7
8
9
10
11
|
function A() {
this.name = '星空海绵';
}
A.prototype.say = function () {
setTimeout(this.read.bind(this), 1000)
}
A.prototype.read = function () {
console.log(this.name);
}
let b = new A();
b.say();
|
在默认情况下,使用 setTimeout() 时,this 关键字会指向 window (或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。
(2)arg1, arg2, …
当bind被函数调用时,传给调用函数的参数列表
3.手写 bind()
😎😎😎😎😎
(1)不支持 new 调用 bind() 返回的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
let obj = {
age: 18
}
function getInfo(name, job) {
console.log(name);
console.log(job);
console.log(this);
console.log(this.age);
}
// getInfo.call(obj, '星空海绵', 'Software Engineer');
Function.prototype.myBind = function (context) {
var _that = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
context = context || window;
return function () {
return _that.apply(context, args.concat(slice.call(arguments)))
}
}
var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
b = new a(), // 上面getInfo中的this打印结果为 obj 对象
c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象
|
从上面的代码中我们可以看出来,此时当使用 new 调用时,并不会忽略掉传入的第一个参数 obj,调用函数的 this 一直是传入的第一个参数,不符合上面列举的原生的 bind() 第一个参数的第3条,当使用 new 调用时,会忽略第一个参数。
(2)支持 new 调用
上面第一种其实已完成了原生bind的基本功能,可以改变this指向了,但是不支持new调用的时候,那我们来思考一下为什么。
- 当使用 new 调用我们写的myBind函数返回的函数时,这就是new通过构造函数创建一个对象的过程,MDN new创建对象过程
1.首先创建一个空对象({})
2.链接该对象(设置该对象的constructor)到另一个对象
3.将步骤1新创建的对象作为this的上下文
4.如果该函数没有返回对象,则返回this
看了上面new创建对象的过程,主要是第3步,将新创建的对象作为this的上下文,我们再看上面的代码,返回的函数中的this如果你去打印的话发现确实是空对象,符合上面new的创建过程,但是,下面一行用apply将调用函数getInfo中的this指向改到了传入的obj,这就不对了,所以说当用new调用的时候,此时就应该忽略obj,那么我们就需要判断new调用和不调用的时候就行了,ES6中有 new.target 可以判断一个函数是否被new调用过,那么ES5中是否有方法可以判断,没有的话那么模拟一个new.target就行了
用new.target的话是下面这两种写法都行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
let obj = {
age: 18
}
function getInfo(name, job) {
console.log(name);
console.log(job);
console.log(this);
console.log(this.age);
}
// getInfo.call(obj, '星空海绵', 'Software Engineer');
Function.prototype.myBind = function (context) {
var _that = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1),
obj = {};
context = context || window;
obj.__proto__.constructor = _that.prototype.constructor;
return function () {
if (new.target) {
return _that.apply(obj, args.concat(slice.call(arguments)))
} else {
return _that.apply(context, args.concat(slice.call(arguments)))
}
}
}
var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
b = new a(), // 上面getInfo中的this打印结果为 b 对象
c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Function.prototype.myBind = function (context) {
var _that = this,
args = [].slice.call(arguments, 1);
context = context || window;
return function () {
if (new.target) {
return new _that(...args.concat(...arguments))
} else {
return _that.apply(context, args.concat([].slice.call(arguments)))
}
}
}
|
不使用 ES6 的new.target:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
let obj = {
age: 18
}
function getInfo(name, job) {
console.log(name);
console.log(job);
console.log(this);
console.log(this.age);
}
// getInfo.call(obj, '星空海绵', 'Software Engineer');
Function.prototype.myBind = function (context) {
var _that = this,
args = [].slice.call(arguments, 1);
context = context || window;
return function F() {
if (this instanceof F) {
return new _that(...[...args, ...arguments]);
}
return _that.apply(context, [...args, ...arguments])
}
}
// getInfo.myBind(obj, '星空海绵', 'Software Engineer')()
var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
b = new a(), // 上面getInfo中的this打印结果为 b 对象
c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象
|
上面代码中this如果在F构造函数的原型链上,那就证明使用new调用了myBind返回的构造函数。
总结:手写bind遇到new调用的问题,关键是需要理解new调用构造函数创建对象的过程。