深入理解js中的this
this的作用
没有this的话,很多问题也可以解决,但是如果没有this会非常不方便,需要反复修改代码。
全局环境下指向window对象,node下指向空对象
this在函数中的指向,在函数执行的时候才真正知道this的指向。他是动态绑定的
this的定义的位置没有关系,跟函数被调用的方式与调用的主题有关系。
默认绑定(独立函数调用)
独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用,直接使用独立函数调用
// 1.案例一:
function foo() {
console.log(this)
}
foo()//window
//2.案例二
function foo1() {
console.log(this)
}
function foo2() {
console.log(this)
foo1()
}
function foo3() {
console.log(this)
foo2()
}
foo3()// window window window
// 3.案例三:
var obj = {
name: "why",
foo: function() {
console.log(this)
}
}
var bar = obj.foo
bar() // window
//4.案例四
function foo() {
console.log(this)
}
var obj = {
name: "why",
foo: foo
}
var bar = obj.foo
bar() // window
//5.案例五
function foo() {
function bar() {
console.log(this)
}
return bar
}
var fn = foo()
fn() // window
隐式绑定(object.fn())
通过某个对象进行调用,object对象会被js引擎绑定到fn函数中的this里面
//1.案例一:
function foo() {
console.log(this)
}
var obj = {
name: "why",
foo: foo
}
obj.foo() // obj对象
// 2.案例二:
var obj = {
name: "why",
eating: function() {
console.log(this.name + "在吃东西")//this指向object
},
running: function() {
console.log(obj.name + "在跑步")
}
}
obj.eating()//why在吃东西
obj.running()//why在跑步
// 3.案例三:
var obj1 = {
name: "obj1",
foo: function() {
console.log(this)
}
}
var obj2 = {
name: "obj2",
bar: obj1.foo
}
obj2.bar()//obj2
案例三图解:
打印出来的this指向的是object2.虽然bar引用的是object1的函数,由于是object 2
调用的bar(),所以this指向的是object2
显式绑定
隐式绑定:必须在调用的对象内部有一个对函数的引用(比如一个属性),间接的将this绑定到了这个对象上。
如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,那么就需要用到call与apply。
通过call与apply明确的绑定了this指向的对象,称之为显示绑定。
//1.call/apply是可以指定this的绑定对象
function foo() {
console.log(this)
}
var obj = {
name: "obj"
}
foo.call(obj)//obj
foo.apply(obj)//obj
foo.apply("abc")//abc
foo.apply("abc")//abc
call与apply
call与apply的区别
传参数不同
// 2.call和apply有什么区别?
function sum(num1, num2, num3) {
console.log(num1 + num2 + num3, this)
}
sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40])
bind
当需要多次使用改变一个函数的this指向时,使用call或者apply需要写很多次。使用bind()改变函数的指向后,后面函数this的指向就不会改变,一直是bind指定的this指向。
function foo() {
console.log(this)
}
foo.call("aaa")
// foo.call("aaa")
// foo.call("aaa")
// foo.call("aaa")
//当默认绑定和显式绑定冲突:优先级:显示绑定>默认绑定。
var newFoo = foo.bind("aaa")
newFoo()//"aaa"
newFoo()//"aaa"
new 绑定
通过new调用一个函数时(构造器),这个时候this是指向调用构造器时创建出来的对象,这个就是new绑定
function Person(name, age) {
this.name = name
this.age = age
}
var p1 = new Person("why", 18)
console.log(p1.name, p1.age)
var p2 = new Person("kobe", 30)
console.log(p2.name, p2.age)
this指向四种绑定规则的优先级
// 显示绑定高于隐式绑定
//1.call/apply
var obj = {
name: "obj",
foo: function() {
console.log(this)
}
}
obj.foo()//obj
// 1.call/apply的显示绑定高于隐式绑定
obj.foo.apply("abc")//"abc"
obj.foo.call("abc")//"abc"
// 2.bind的优先级高于隐式绑定
var bar = obj.foo.bind("cba")
bar()//"cba"
// 2.bind的优先级高于隐式绑定
function foo(){
console.log(this)
}
var obj = {
name: "obj",
foo: foo.call("abc")
}
obj.foo()//"abc"
//结论: new关键字不能和apply/call一起来使用
// new的优先级高于bind
function foo() {
console.log(this)
}
var bar = foo.bind("aaa")
var obj = new bar()//foo{}
// new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)
补充:箭头函数this指向
什么是箭头函数?
相当于于function函数的缩写。
-
没有function,改成=> ,形参小括号写到=>左边
var foo = ()=>{ console.log("foo") } foo()//"foo"
箭头函数的简写
-
简写一:只有一个形参可以省略小括号
var foo = item =>{ console.log(item) } foo(1)//1
-
简写二:箭头函数函数体只有一行代码,可以省略大括号
-
强调: 并且它会默认将这行代码的执行结果作为返回值
var foo = item => console.log(item) foo(2)//2
-
简写三: 如果一个箭头函数, 只有一行代码, 并且返回一个对象, 这个时候如何编写简写
//正常写法
var bar = () => {
return { name: "why", age: 18 }
}
// 简写
var bar = () => ({ name: "why", age: 18 }) //如果不加(),函数的{}与对象的{},v8引擎在进行词法解析的时候会报错。
console.log(bar())//{name: 'why', age: 18}
特殊的绑定规则
function foo() {
console.log(this)
}
foo.apply("abc")//"abc"
foo.apply({})//{}
// apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象
foo.apply(null)
foo.apply(undefined)
var bar = foo.bind(null)
bar()
var obj1 = {
name: "obj1",
foo: function () {
console.log(this)
}
}
var obj2 = {
name: "obj2"
};
// obj2.bar = obj1.foo
// obj2.bar()
(obj2.bar = obj1.foo)()//window
// (obj2.bar = obj1.foo)为一个整体,object2的bar引用obj1的foo函数
//本质上还是一个独立函数调用,于是this指向window
箭头函数的this指向
箭头函数并不绑定this对象,箭头函数本身不受隐式绑定以及call、apply的影响,那么this引用只会从上层作用域中找到对应的this
//案例一
var name = "window"
var foo = () => {
console.log(this.name)
}
foo()//window
var obj = {
name: "obj",
foo: foo
}
obj.foo()//window
foo.call("abc")//window
//案例二
var name = "window"
var foo = {
name: "foo",
fn: () => {
console.log(this.name)
}
}
foo.fn()//window
//案例三
var name = "window"
var foo = {
name: "foo",
fn: function () {
return () => {
console.log(this)
}
}
}
foo.fn()()//foo
this指向面试题
面试题一:
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window: 独立函数调用
person.sayName(); // person: 隐式调用
(person.sayName)(); // person: 隐式调用
(b = person.sayName)(); // window: 赋值表达式(独立函数调用)
}
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(隐式绑定)
person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)
person1.foo2(); // window(不绑定作用域,上层作用域是全局)
person1.foo2.call(person2); // window
person1.foo3()(); // window(独立函数调用)
person1.foo3.call(person2)(); // window(独立函数调用)
person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)
person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2); // person1(上层找到person1)
面试题3:
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
person1.foo1.call(person2) // person2(显示高于隐式绑定)
person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)
person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
面试题4:
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
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
call、apply、bind的js实现
call,apply、bind三种方法js实现的核心思路
- 1.获取到真实需要调用的函数,当函数调用hycall时,this指向调用hycall的函数(隐式绑定)
- 2.使用隐式绑定,把指向调用函数的this改为为指向输入的thisArg参数。
- 3.执行调用函数
- 4.将最终的结果返回出去
剩下的就是一些边界值处理,以及call,apply,bind参数传递的不同
call实现
// 自己实现hycall·
Function.prototype.hycall = function (thisArg, ...args) {
// 1.获取到真实需要调用的函数
var fn = this;
// 2.对thisArg转成对象类型(防止它传入的是非对象类型),非对象就不可以进行隐式绑定
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
//2.用隐式绑定,把调用函数的this指向改为为输入的thisArg参数
thisArg.fn = fn
//执行调用函数
var result = thisArg.fn(...args)
delete thisArg.fn
// 4.将最终的结果返回出去
return result
}
//调用hycall的函数不带参
function foo() {
console.log("foo函数被执行", this)
}
foo.hycall("abc")//foo函数被执行 String {'abc', fn: ƒ}
foo.hycall(null)//foo函数被执行 Window
foo.hycall(undefined)//foo函数被执行 Window
//调用hycall的函数带参
function sum(num1, num2) {
console.log("sum函数被执行", this)
return num1 + num2
}
var result = sum.hycall("abc", 20, 30)//sum函数被执行 String {'abc', fn: ƒ}
console.log("hycall的调用:", result)//hycall的调用: 50
apply的实现
// 自己实现hyapply
Function.prototype.hyapply = function (thisArg, argArray) {
//1.获取到真实需要调用的函数
var fn = this
// 2.处理绑定的thisArg
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
//3.argArray如果没有传值,赋一个空数组
argArray = argArray ? argArray : []
//argArray = argArray || []
//4.用隐式绑定,把调用函数的this指向改为为输入的thisArg参数
thisArg.fn = fn
var result = thisArg.fn(...argArray)//...argArray将数组argArray展开为以逗号分割的参数序列,传入给调用函数
delete thisArg.fn
// 5.返回结果
return result
}
//调用hyapply的函数不带参
function foo() {
console.log("foo函数被执行", this)
}
foo.hycall("abc")//foo函数被执行 String {'abc', fn: ƒ}
foo.hycall(null)//foo函数被执行 Window
foo.hycall(undefined)//foo函数被执行 Window
//调用hyapply的函数带参
function sum(num1, num2) {
console.log("sum函数被执行", this)
return num1 + num2
}
var result = sum.hyapply("abc", 20, 30)//sum函数被执行 String {'abc', fn: ƒ}
console.log("hycall的调用:", result)//hycall的调用: 50
bind的实现
// 自己实现hybind
Function.prototype.hybind = function (thisArg, ...argArray) {
// 1.获取到真实需要调用的函数
var fn = this
// 2.对thisArg转成对象类型(防止它传入的是非对象类型),非对象就不可以进行隐式绑定
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
function proxyFn(...args) {
// 3.将函数放到thisArg中进行调用
thisArg.fn = fn
// 特殊: 对两个传入的参数进行合并
var finalArgs = [...argArray, ...args]
//4.执行调用hybind函数
var result = thisArg.fn(...finalArgs)
delete thisArg.fn
// 5.返回结果
return result
}
return proxyFn
}
//调用hybind函数不带参
function foo() {
console.log("foo被执行", this)
}
var newfoo = foo.hybind("bbb")
var add = newfoo()//foo被执行 String {'bbb', fn: ƒ}
//调用hybind函数带参
function sum(num1, num2, num3, num4) {
console.log("sum函数调用hycall",this)
return num1+num2+num3+num4
}
var newSum = sum.hybind("abc", 10, 20)//newSum指向return出来的proxyFn函数
var result = newSum(30, 40)//sum函数调用hycall String {'abc', fn: ƒ}
console.log(result)//100
// var newSum = sum.hybind("bbb",10,20,30,40)//sum函数调用hycall String {'bbb', fn: ƒ}
// var result = newSum()
// console.log(result)//100