为什么需要this
this用于:当对象的方法被调用时,在被调用的方法中获取调用当前方法的对象
function Person(name) {
this.name = name;
this.eatting = function () {
console.log(this.name + "在吃东西");
};
this.running = function () {
console.log(this.name + "在奔跑");
};
this.studying = function () {
console.log(this.name + "在学习");
};
}
var why = new Person("why");
why.eatting();
why.studying();
why.running();
var lily = new Person("lily");
lily.eatting();
lily.studying();
lily.running();
全局作用域下的this
在浏览器环境下,全局作用域下(即全局执行上下文中)的this指向window(即globalObject)
在Node环境下,全局作用域下的this指向{}
console.log(this)
但是,开发中很少直接在
全局作用域
下去使用this,通常都是在函数中使用
。
- 所有的函数在被调用时,都会创建一个执行上下文:
- 这个上下文中记录着函数的调用栈、AO对象等;
- this也是其中的一条记录;
this到底指向什么
我们先来看一个让人困惑的问题:
- 定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
function foo() {
console.log(this);
}
var obj = {
name: "why",
foo: foo
};
foo() //this -> window
obj.foo() //this -> obj;
foo.call('call') // this -> String{"call"}
这个的案例可以给我们什么样的启示呢?
- 1.函数在调用时,JavaScript会默认给this绑定一个值;
- 2.this的绑定和定义的位置(编写的位置)没有关系;
- 3.this的绑定和调用方式以及调用的位置有关系;
- 4.this是在运行时被绑定的;
那么this到底是怎么样的绑定规则呢?一起来学习一下吧
- 绑定一:默认绑定;
- 绑定二:隐式绑定;
- 绑定三:显示绑定;
- 绑定四:new绑定;
this的绑定规则
默认绑定
什么情况下使用默认绑定呢?独立函数调用。
- 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
在浏览器环境下,
独立函数调用
,函数内部的this
会默认被绑定为window
跟函数的调用位置无关
案例1
function foo1() {
console.log(this); //window
}
function foo2() {
console.log(this); //window
foo1();
}
function foo3() {
console.log(this); //window
foo2();
}
foo3();
案例2
var obj = {
name: "why",
foo: function () {
console.log(this);
},
};
var bar = obj.foo;
bar(); //window
案例3
function foo() {
console.log(this);
}
var obj1 = {
name: "why",
foo: foo,
};
var bar1 = obj.foo;
bar1(); //window
案例4
function foo4() {
return function baz() {
console.log(this);
};
}
var baz = foo4();
baz(); //window
隐式绑定
另外一种比较常见的调用方式是通过某个对象进行调用的:
- 也就是它的调用位置中,是通过某个对象发起的函数调用。
函数以对象的方法
被调用,js引擎会在函数的执行上下文中,把当前调用此函数的对象
,隐式
绑定到this
上
案例1
function foo() {
console.log(this);
}
var obj = {
name: "why",
foo: foo,
};
obj.foo(); //this
案例二
var person = {
name: "why",
eatting: function () {
console.log(this);
},
running: function () {
console.log(this);
},
};
person.eatting(); //person对象
person.running(); //person对象
案例三
function foo1() {
console.log(this);
}
var obj1 = {
name: "why",
foo: foo,
};
var obj2 = {
name: "why",
foo: obj1.foo,
};
obj2.foo(); //obj2对象
显示绑定
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性);
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
- 正是通过这个引用,间接的将this绑定到了这个对象上;
如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
- JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
- 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的。
- 在调用这个函数时,会将this绑定到这个传入的对象上。
因为上面的过程,我们明确的绑定了this指向的对象,所以称之为 显示绑定
。
使用call/apply/bind
可以显示的
为函数执行上下文中的this绑定对象
案例1:
foo独立调用,函数执行上下文中的this会被默认绑定为全局对象wiondow
如果想在函数foo执行时,其创建的执行上下文中的this绑定为obj对象,但时又不想给obj对象添加foo方法
可以使用call/apply/bind显示的把foo函数执行上下文中的this绑定为obj对象
function foo() {
console.log(this);
}
var obj = {
name: "why",
};
foo.call(obj); //obj对象
foo.apply(obj); //obj对象
//foo.bind(obj)执行的结果会返回一个全新的函数,返回的函数的执行上下文的this被显示的绑定为了obj
var bar = foo.bind(obj)
bar() //obj
案例2
foo.bind(obj)执行的结果会返回一个全新的函数,返回的函数的执行上下文的this被显示的绑定为了obj
function foo() {
console.log(this);
}
var obj = {
name: "why",
};
var bar = foo.bind(obj)
bar() //obj对象
new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数是,会执行如下的操作:
- 1.创建一个全新的对象;
- 2.这个新对象会被执行prototype连接;
- 3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 4.如果函数没有返回其他对象,表达式会返回这个新对象;
通过new 执行函数构造器的时候,会创建一个新对象,
并把函数构造器执行时创建的执行上下文中的this绑定为这个新对象
如果函数构造器没有返回值,会默认返回这个新对象
function Person(name, age) {
this.name = name;
this.age = age;
}
var why = new Person("why", 18);
console.log(why.name, why.age);
var kobe = new Person("kobe", 40);
console.log(kobe.name, kobe.age);
一些高阶函数的回到函数中的this分析
1.setTimeout函数
setTimeout函数的回调函数在其内部是被独立调用的,
所以,回调函数执行时创建的执行上下文中的this指向window
setTimeout(function () {
console.log(this); //this=> window
}, 3000);
模拟setTimeout函数的实现
function hySetTimeout(callback, duration) {
callback();
}
2.onClick函数
当js引擎监听到boxDiv被点击时,会调用boxDiv.onClick()
,
onClick函数执行上下文内部的this被隐式绑定为触发事件的DOM元素对象
var boxDiv = document.querySelector(".box");
boxDiv.onClick = function () {
console.log(this); //this -> boxDiv
};
3.addEventListener函数
var box1Div = document.querySelector(".box1");
box1Div.addEventListener("click", function () {
console.log(this); //this-> box1Div
});
box1Div.addEventListener("click", function () {
console.log(this);//this-> box1Div
});
box1Div.addEventListener("click", function () {
console.log(this);//this-> box1Div
});
模拟实现
var eventMap = new Map();
HTMLDivElement.prototype.addEventListener = function (eventType, eventFn) {
var eventArray = eventMap.get(eventType);
if (!eventArray) {
eventMap.set(eventType, new Set());
} else {
//把监听函数添加到事件数组
eventArray.add(eventFn);
}
};
//当监听到事件触发时,迭代eventArray中的函数,
eventMap.get("click").forEach((fn) => {
fn.call(box1Div);
});
4.forEach/map/filter/find
var names = ["lily", "why", "kobe"];
names.forEach(function (item) {
console.log(item, this); //this -> window
});
names.map(function (item) {
console.log(item, this); //this -> window
});
//forEach/map回调函数执行时的执行上下文中this会被显示绑定到第二个参数对象
//如果第二个参数不为对象,会转换为对象
names.forEach(function (item) {
console.log(item, this); //this -> String{"abc"}
}, "abc");
names.map(function (item) {
console.log(item, this); //this -> String{"abc"}
}, "abc");
this绑定规则的优先级
学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多
条规则,优先级谁更高呢?
1.默认规则的优先级最低
- 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
2.显示绑定优先级高于隐式绑定
代码测试
//1.显示绑定优先级高于隐式绑定
//案例1
var obj = {
name: "obj",
foo: function () {
console.log(this);
},
};
obj.foo.call("call"); // this -> String{"call"}
//案例2
function foo() {
console.log(this);
}
var obj1 = {
name: "obj",
foo: foo.bind("coder"),
};
obj1.foo(); // this -> String{"coder"}
3.new绑定优先级高于隐式绑定
代码测试
//2.new绑定优先级高于隐式绑定
//案例1
var obj = {
name: "obj",
foo: function () {
console.log(this);
},
};
var f = new obj.foo() //this -> foo {}
4.new绑定优先级高于bind
- new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
- new绑定可以和bind一起使用,new绑定优先级更高
代码测试
//2.new绑定优先级高于显式绑定
//案例1
function foo() {
console.log(this);
}
var bar = foo.bind("aaa");
var obj = new bar(); //this -> foo {}
this规则之外
1.忽略显示绑定
apply/call/bind内部,做了判断,如果第一个参数为undefined或null, 给函数执行上下文内部的this绑定为window
function foo() {
console.log(this);
}
foo.apply("aaa"); //this -> String {"aaa"}
foo.apply({}); //this -> {}
//apply/call/bind内部,做了判断,如果第一个参数为undefined或null, 给函数执行上下文内部的this绑定为window
foo.apply(undefined); //this -> window
foo.apply(null); //this -> window
foo.call(undefined); //this -> window
foo.call(null); //this -> window
var bar = foo.bind(null);
var baz = foo.bind(undefined);
bar(); //this -> window
baz(); //this -> window
2. 间接函数引用
var obj = {
name: "obj",
foo: function () {
console.log(this);
},
};
var obj1 = {
name: "why",
};
(obj1.bar = obj.foo)(); //this -> window
//obj1.bar = obj.foo返回obj.foo这个函数对象的16进制引用地址,
//函数对象引用地址()调用,是独立函数调用
需要注意:下面代码不能执行:obj对象后面没有分号
var obj = {
name: "obj",
foo: function () {
console.log(this);
},
};
var obj1 = {
name: "why",
}
(obj1.bar = obj.foo)();
因为代码在解析阶段,会把下面当成一段代码
var obj1 = {
name: "why",
}
(obj1.bar = obj.foo)();
比如我不加分号的情况下,格式化代码:
var obj = {
name: "obj",
foo: function () {
console.log(this);
},
};
var obj1 = {
name: "why",
}((obj1.bar = obj.foo))();
3.ES6箭头函数
箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁:
- 箭头函数不会绑定
this
、arguments
属性; - 箭头函数不能作为
构造函数
来使用(不能和new一起来使用,会抛出错误);
箭头函数如何编写呢?
- (): 函数的参数
- {}: 函数的执行体
var sum = (num1, num2, num3) => {
console.log(num1 + num2 + num3);
};
//高阶函数在使用时也可以传入箭头函数
var nums = [2, 34, 67];
nums.forEach((item, index, arr) => {
console.log(item, index, arr);
});
箭头函数的编写优化
优化一: 如果只有一个参数()可以省略
//1.如果参数只有一个,小括号可以省略
nums.forEach((item) => {
console.log(item);
});
优化二: 如果函数执行体中只有一行代码, 那么可以省略大括号
- 并且这行代码的返回值会作为整个函数的返回值
//2.如果函数执行体中只有一行代码, 那么可以省略大括号,并且这行代码的执行结果会作为整个函数的返回值
nums.forEach((item) => console.log(item));
var doubleNums = nums.map((item) => item * 2);
console.log(doubleNums);
var newNums = nums
.filter((item) => item % 2 === 0)
.map((item) => item * 100)
.reduce((preResult, item) => preResult + item);
console.log(newNums);
优化三: 如果函数执行体只有返回一个对象, 那么需要给这个对象加上()
//3. 如果函数执行体只有返回一个对象, 那么需要给这个对象加上()
var foo = () => {
return { name: "why", age: 18 };
};
var bar = () => ({ name: "why", age: 18 });
this规则之外 – ES6箭头函数
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this
。
我们来看一个模拟网络请求的案例:
- 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
- 我们需要拿到obj对象,设置data;
- 但是直接拿到的this是window,我们需要在外层定义:var _this = this
- 在setTimeout的回调函数中使用_this就代表了obj对象
//箭头函数之前的解决方案
var obj = {
data: [],
getData: function () {
const _this = this;
setTimeout(function () {
//setTimeout回调函数执行上下文中的this为window
var result = ["abc", "bac"];
_this.data = result;
}, 1000);
},
};
使用箭头函数
var obj = {
data: [],
getData: function () {
setTimeout(() => {
//setTimeout回调函数为箭头函数,
//因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this
var result = ["abc", "bac"];
this.data = result;
}, 1000);
},
};
this面试题
面试题一
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
},
};
function sayName() {
var foo = person.sayName;
foo();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
最终输出结果: window person person window
解析
/**
* 全局执行上下文编译阶段:
* VO->GO GO -> {name: undefined, person: #0x100, sayName: #0x200}
* #0x100 -> {name: "person", sayName: #0x300}
*
* 全局执行上下文执行阶段:
* VO->GO GO -> {name: window, person: #0x100, sayName: #0x200}
*/
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
},
};
function sayName() {
//父级作用域 GO
/**
* 创建sayName函数执行上下文
* 编译阶段:
* VO->AO AO-> {foo:undefined}
* 执行阶段:
* VO->AO AO-> {foo:0x300}
*/
var foo = person.sayName;
foo(); //window 独立函数调用this -> window
person.sayName(); //person 作为对象的方法调用,this被隐式绑定为调用函数的对象person person.name为person
(person.sayName)(); //person 加不加小括号效果一样
(b = person.sayName)(); //window 赋值表达式最后会返回person.sayName的引用地址#0x300, #0x300()是独立函数调用
}
sayName();
面试题二
var name = "window";
var person1 = {
name: "person1",
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name);
};
},
foo4: function () {
return () => {
console.log(this.name);
};
},
};
var person2 = { name: "person2" };
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
执行结果 person person2 window window window window person2 person1 person2 person1
解析
/**
* 全局执行上下文:
* 编译阶段:
* VO-> GO GO {name: undefined, person1: #0x100, person2: #0x200}
* person1: #0x100 {name: 'person1', foo1: #0x101, foo2: #0x102, foo3: # 0x103, foo4: # 0x104}
* person2: #0x200 { name: "person2" }
* 执行阶段: GO {name: "window", person1: #0x100, person2: #0x200}
*/
var name = "window";
var person1 = {
name: "person1",
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name), //父级作用域GO
foo3: function () {
//父级作用域为GO
return function () {
//父级作用域为foo3 AO
console.log(this.name);
};
},
foo4: function () {
//父级作用域为GO
return () => {
//箭头函数不绑定this this为其父级作用域的this
console.log(this.name);
};
},
};
var person2 = { name: "person2" };
person1.foo1(); //person1 隐式绑定this -> person1
person1.foo1.call(person2); //person2 显示绑定this -> person2
person1.foo2(); //window 箭头函数不绑定this this为父级作用域的this
person1.foo2.call(person2); //window 箭头函数显示绑定this无效 this为父级作用域的this
person1.foo3()(); //window 独立函数调用 this->window
person1.foo3.call(person2)(); // window 独立函数调用 this->window
person1.foo3().call(person2); //person2 显示绑定 this为person2
person1.foo4()(); //person1
/**
* person1.foo4()();解析:
* 1.person1.foo4()
* 编译阶段:
* 父级作用域 GO
* AO {anonyFn: #0x401}
* this-> person1
* 2.anonyFn()
* 父级作用域 AO(person1.foo4)
* 箭头函数不绑定this this为父级作用域的this
*/
person1.foo4.call(person2)(); // person2
/**
* person1.foo4.call(person2)();解析:
* 1.person1.foo4.call(person2)
* 执行阶段:
* 父级作用域 GO
* AO {anonyFn: #0x402}
* this被显示绑定为person2
* 2.anonyFn()
* 父级作用域 AO(person1.foo4)
* 箭头函数不绑定this this为父级作用域的this
*/
person1.foo4().call(person2); //person1
/**
* person1.foo4().call(person2);解析:
* 1.person1.foo4()
* 编译阶段:
* 父级作用域 GO
* AO {anonyFn: #0x403}
* this-> person1
* 2.anonyFn()
* 父级作用域 AO(person1.foo4)
* 箭头函数不绑定this,显示绑定this无效 this为父级作用域的this
*/
//执行结果 person person2 window window window window person2 person1 person2 person1
面试题三
var name = "window";
function Person(name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
};
};
this.foo4 = function () {
return () => {
console.log(this.name);
};
};
}
var person1 = new Person("person1")
var person2 = new Person("person2")
person1.foo1()
person1.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
执行结果: person1 person2 person1 person1 window window person2 person1 person2 person1
解析
var name = "window";
function Person(name) {
//父级作用域GO
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
};
};
this.foo4 = function () {
return () => {
console.log(this.name);
};
};
}
var person1 = new Person("person1")
var person2 = new Person("person2")
person1.foo1() //person1
person1.foo1.call(person2) //person2
person1.foo2() //person1 箭头函数不绑定this, this为父级作用域中的this
person1.foo2.call(person2)//person1 箭头函数不绑定this,显示绑定this无效 this为父级作用域中的this
person1.foo3()() //window 独立函数调用 this默认被绑定为window
person1.foo3.call(person2)() //window 独立函数调用 this默认被绑定为window
person1.foo3().call(person2) //person2 显示绑定this为person2
person1.foo4()() //person1 箭头函数不绑定this, this为父级作用域中的this
person1.foo4.call(person2)()//person2 箭头函数不绑定this, this为父级作用域中的this
person1.foo4().call(person2)//person1 箭头函数不绑定this,显示绑定this无效 this为父级作用域中的this
面试题四
var name = "window";
function Person(name) {
this.name = name;
this.obj = {
name: "obj",
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
},
};
}
var person1 = new Person("person1");
var person2 = new Person("person2");
person1.obj.foo1()(); //window 独立函数调用
person1.obj.foo1.call(person2)(); //window 独立函数调用
person1.obj.foo1().call(person2); //person2 显示绑定this
person1.obj.foo2()(); //obj 箭头函数不绑定this,this为上级作用域的this
person1.obj.foo2.call(person2)(); //person2 箭头函数不绑定this,this为上级作用域的this
person1.obj.foo2().call(person2); //obj 箭头函数不绑定this,显示绑定无效,this为上级作用域的this
非常感谢王红元老师的深入JavaScript高级语法让我学习到很多 JavaScript
的知识