作用域

  • 全局作用域 - window
  • 局部作用域 - js 当中只有函数具备块级作用域

异同:

改变函数体内 this 的指向。
不同之处:

  • call 严格模式
  • call 非严格模式
  • apply 和 call 基本上一致,唯一区别在于传参方式
  • bind 语法和 call 一模一样,区别在于立即执行还是等待执行,bind 不兼容 IE6~8。

案例

函数是在哪里被谁调用的。也就是说 this 指向谁,跟函数在哪里定义没有关系,而是取决于被谁调用。

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this.bar);
}
var bar = "bar1";
var o2 = { bar: "bar2", foo: foo };
var o3 = { bar: "bar3", foo: foo };

foo(); /* "bar1" – 默认绑定 */
o2.foo(); /* "bar2" – 隐式绑定 */
o3.foo(); /* "bar3" – 隐式绑定 */

默认情况,在非严格模式下,this 指向全局对象,在浏览器中就是 window
严格模式,this 为 undefined
隐式绑定,foo 作为 o2 的方法来调用,就指向 o2

1
2
3
4
5
6
7
8
9
function foo() {
console.log(this.bar);
}

var bar = "bar1";
var obj = { bar: "bar2" };

foo(); // 默认绑定
foo.call(obj); // 显式绑定

通过 call、apply 或者 bind 调用的,那么这种调用就是显式绑定这种绑定中,this 的指向就是这三个函数中传递的第 一个参数。

call:

当前实例(函数)通过原型链查找机制找到 function.prototpye 上 call 方法。
把找到的 call 方法执行 当 call 方法执行的时候,内部处理了一些事情 1.首先把要操作的函数中的 this 关键字变为 call 方法第一个传递的实参 2.把 call 方法第二个及之后的实参获取到 3.把要操作的函数执行,并且把第二个以后传递进来的实参传递给函数
call 中细节
在非严格模式下
如果不传参数,或者第一个参数是 null 或 nudefined,this 都指向 window

1
2
3
4
5
6
7
8
9
10
const fn = function (a, b) {
console.log(this, a, b);
};
const val = { name: "张三" };

fn.call(val, 1, 2); // this:val, a: 1, b: 2
fn.call(1, 2); // this:1, a: 2, b: undefined
fn.call(); // this:window, a: undefined, b: undefined
fn.call(null); // this: window, a: undefined, b: undefined
fn.call(undefined); // this: window, a: undefined, b: undefined

在严格模式下
第一个参数是谁,this 就指向谁,不传就为 undefined

1
2
3
4
5
6
7
8
9
10
"use strict";
let fn = function (a, b) {
console.log(this, a, b);
};
let obj = { name: "obj" };
fn.call(obj, 1, 2); // this:obj a:1 b:2
fn.call(1, 2); // this:1 a:2 b=undefined
fn.call(); // this:undefined a:undefined b:undefined
fn.call(null); // this:null a:undefined b:undefined
fn.call(undefined); // this:undefined a:undefined b:undefined

apply

apply 把需要传递给 fn 的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给 fn 一个个的传递

1
2
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);

bind

bind 与 call 的 区别在一个是立即执行函数,一个是等待执行

1
2
document.onclick = fn.call(obj);
// call把this改为obj,立即执行fn函数,点击时函数返回值为undefined
1
2
document.onclick = fn.bind(obj);
// bind把this预处理为obj,但是函数没有执行,当点击时才会执行

实现 call

1
2
3
4
5
Function.prototype.myCall = function (obj, ...args) {
obj.fn = this;
obj.fn(...args);
delete obj.fn;
};

实现 apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.myCall = function (context = window, args) {
if (this === Function.prototype) {
return undefined;
}
if (typeof this !== "function") {
throw new TypeError("error");
}
let res;
const fn = Symbol();
context[fn] = this;
if (args.constructor !== Array) {
return new TypeError("不是数组!");
}
res = context[fn](args);
delete context[fn];
return res;
};

实现 bind

总的来说 bind 有如下三个功能点:

  1. 改变原函数的 this 指向,即绑定上下文,返回原函数的拷贝。
  2. 当 绑定函数 被调用时,bind 的额外参数将置于实参之前传递给被绑定的方法。
  3. 注意,一个 绑定函数 也能使用 new 操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.myBind = function (thisArg) {
if (typeof this !== "function") {
return;
}
var _self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fnBound = function () {
// 检测 New
// 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
var _this = this instanceof _self ? this : thisArg;
return _self.apply(
_this,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 为了完成 new操作
// 还需要做一件事情 执行原型 链接 (思考题,为什么?
fnBound.prototype = this.prototype;
return fnBound;
};

案例

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
showName: function () {
console.log(this.name);
},
};
var person = new Person("xxx");
person.showName();
1
2
3
var animal = {
name: "yyy",
};

我想让 person.showName()运行后 name 变成 yyy,该如何操作?

1
2
3
person.showName.call(animal);
person.showName.apply(animal);
person.showName.bind(animal)(); //不会立即执行,需要调用

他们改变了 this 的指向。
具体分析二(call、apply 的区别:接受参数的方式不一样。

1
2
3
var arr = [1, 5, 9, 3, 5, 7];
Math.max.apply(Math, arr);
Math.max.call(Math, 1, 5, 9, 3, 5, 7);

apply 的第二个参数必须是一个包含多个参数的数组(或类数组对象)