一、this 引入
1.1 引入
- 普通函数中都有一个this变量:一般情况指向某一个对象
- 箭头函数没有 this
- 谁调用就指向谁
1.2 现象理解
<script>
// 普通函数中都有一个this变量:在大多数情况下会指向一个对象
// arguments:保存的是传入的所有参数
// 1. 情况一:普通函数被默认调用,this指向window
function foo(name, age) {
console.log(arguments);
console.log(this);//window 对象
}
foo("lili", 123)
function sayHello() {
console.log(this);
}
sayHello()// window
// 2. 情况二:函数是被某一个对象引用并且调用它,this会指向这个对象(调用的那个对象)
var obj = {
// 函数没有谁属于谁,看谁调用的,谁就是this
running: function () {
console.log("I LIKE RUNNING");
console.log(this === obj);
}
}
obj.running()//true
// 将running这个函数赋值给fn
// 引用的赋值
// 题目一:
var fn = obj.running
fn()//window
// 题目二:
function bar() {
console.log("bar function~~");
console.log(this);
}
var person = {
name: "lili",
bar: bar
}
person.bar()//person
</script>
1.3 为什么要使用 this
- 开发中作用:方便在一个方法拿到当前对象的一些属性
- 当前对象不一定是 info
- 不受外界变量的影响
- 不然就是每次传入参数
<script>
// 对象没有作用域,只是一种数据类型
//{key:value}:对象不是代码块
var info = {
name: "lili",
running: function () {
console.log(this.name + " is running");
},
eating: function () {
console.log(this + " is eating");
},
studying: function () {
console.log(this + " is studying");
}
}
info.running()
info.eating()
info.stydying()
</script>
二、开启 this 之旅
2.1 this 的绑定规则
-
调用:执行
-
函数在调用时, JavaScript 会默认给 this 绑定一个值
-
this 的绑定和定义的位置(编写的位置)没有关系
-
this 的绑定和调用方式以及调用的位置有关
- 调用方式
- 调用位置
-
this 是在运行时被绑定的
- 跟参数类似
this的绑定规则
-
默认绑定独立函数调用: window ,就是普通的一个函数进行调用
- 普通的函数被独立的调用
- 函数定义在对象中,进行赋值然后独立调用
- 高阶函数
- 严格模式下, 独立调用的函数中的 this 指向的是 undefined
// "use strict" function foo(){ console.log("1. 独立函数调用,对象是window"); } foo()//对象是 window // 2. 函数定义在对象中,但是独立调用 var obj={ name:"lili", bar:function(){ console.log("谁调用:",this); } } obj.bar()//对象调用 var baz= obj.bar//函数赋值 baz()//独立函数调用 window // 3.高阶函数 function test(fn){ fn() } test(obj.bar)//把这个值赋值给fn了 window // 4. 严格模式下,独立调用的函数中的this指向的是underfined
- 隐式绑定:通过某个对象进行调用,js 引擎默认绑定到函数调用者
function foo(){ console.log("foo函数:",this); } var obj={ bar:foo } obj.bar() // obj
- 显式绑定:明确告诉它指向谁
function foo(){ console.log("foo函数:",this);//强制this指向obj } var obj={ name:"lili" } //可能会这么做 // obj.foo=foo//添加属性到obj // obj.foo() // 思考:直接执行函数并且强制 this 就是 obj 对象 // foo.apply,与foo.call区别很小 foo.call(obj) // 传入为普通类型-->创建对应的包装类型-->作为 this //foo.call(undefined) // 没有包装类型-->指向window
- new 绑定
function foo(){ console.log("foo函数:",this); } // new: // 1. 创建新的空对象 // 2. 将this指向这个空对象 // 3. 执行函数中的代码 // 4. 没有显式返回非空对象时候,默认返回这个对象 new foo()//函数可以作为一个类
2.2 apply/call/bind
- call
- 参数
- 第一个参数绑定 this
- 后续参数作为实参传入函数,参数列表
- 参数
- apply
- 参数
- 第一个参数绑定 this
- 第二个参数(传入真正的实参),以数组的形式写,会将每一个参数赋值给形参
- 参数
// call/apply
function foo(name, age, height) {
console.log("foo函数被调用:", this)
console.log("打印参数:", name, age, height)
}
// ()调用
// foo("whf", 18, 1.88)
// apply
// 第一个参数: 绑定this
// 第二个参数: 传入额外的实参, 以数组的形式
// foo.apply("apply", ["kobe", 30, 1.98])
// call
// 第一个参数: 绑定this
// 参数列表: 后续的参数以多参数的形式传递, 会作为实参
foo.call("call", "james", 25, 2.05)
-
两者区别
- 都可以调起函数,都可以绑定一个 this
- 绑定实参的时候格式不一样
-
bind():创建一个绑定函数BF,比较少用
- 绑定函数是一个怪异函数对象(看起来是一个独立的函数,但是已经绑定好了 this )
- 其他参数:参数也可以绑定
- 代码阅读性放在第一位
function foo(){ console.log("foo:",this); } var obj={name:"whuuuy"} // 需求:调用 foo 时,总是绑定到obj对象,又不需要obj对象身上有函数 foo.apply(obj)//每次都需要apply(obj) // bind函数登场: var bar=foo.bind(obj)//绑定obj对象 bar()//依然指向foo函数,但是foo中的this绑定了obj
- 其他参数
// bind函数的其他参数(了解) var bar = foo.bind(obj, "kobe", 18, 1.88) bar("james")//会传到最后一个
2.3 内置函数的调用绑定
- 根据经验
- 框架可能会说
<button>按钮</button>
<script>
// 内置函数(第三方库): 根据一些经验
// 1.定时器 window
// setTimeout(function() {
// console.log("定时器函数:", this)
// }, 1000)
// 2.按钮的点击监听
// var btnEl = document.querySelector("button")
// btnEl.onclick = function() {
// console.log("btn的点击:", this)
// }
// btnEl.addEventListener("click", function() {
// console.log("btn的点击:", this)
// }) //btnEl
// 3.forEach window
// 第一个参数:回调函数
// 第二个参数:绑定 this
var names = ["abc", "cba", "nba"]
names.forEach(function(item) {
console.log("forEach:", this)
}, "aaaa")
2.4 规则优先级
- 默认绑定(独立函数调用)的优先级最低
- 显式绑定的优先级高于隐式绑定
- new 绑定优先级高于隐式绑定
- new 不能 和 apply、call 一起使用,没有可比性
- new 的优先级高于 bind 优先级
- bind 的优先级高于 apply,call
- 但是 bind 很少用
- new ( 新对象 ) > bind > apply、call > 隐式绑定 > 默认绑定
// function foo() {
// console.log("foo:", this)
// }
// 比较优先级:
// 1.显式绑定绑定的优先级高于隐式绑定
// 1.1.测试一:apply 高于隐式绑定
// var obj = { foo: foo }
// obj.foo.apply("abc")
// obj.foo.call("abc")
// 1.2.测试二:bind 高于隐式绑定
// var bar = foo.bind("aaa")
// var obj = {
// name: "iii",
// baz: bar
// }
// obj.baz() //aaa
// 2.new 绑定优先级高于隐式绑定
// var obj = {
// name: "whkkkkpy",
// foo: function() {
// console.log("foo:", this)
// console.log("foo:", this === obj)
// }
// }
// new obj.foo() 指向空对象
// 3.new/显式
// 3.1. new不可以和apply/call一起使用
// 3.2. new优先级高于bind
// function foo() {
// console.log("foo:", this)
// }
// var bindFn = foo.bind("aaa")
// new bindFn() //空对象
// 4.bind/apply优先级
// bind优先级高于apply/call
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
bindFn.call("bbb")
2.5 this规则之外
- 显式绑定传入 null/undefined , 那么使用的规则是默认绑定
- 间接函数引用,使用默认绑定规则
// 1.情况一: 显式绑定null/undefined, 那么使用的规则是默认绑定
// function foo() {
// console.log("foo:", this)
// }
// foo.apply("abc")
// foo.apply(null) window
// foo.apply(undefined) window
// 2.情况二: 间接函数引用
var obj1 = {
name: "obj1",
foo: function () {
console.log("foo:", this)
}
}
var obj2 = {
name: "obj2"
};
// ()小括号:1. 当成整体 2. 函数调用
// {}[]()前要加;分号
// obj2.foo = obj1.foo
// obj2.foo()
// 开发中不能这么写
(obj2.foo = obj1.foo)()//独立函数调用
- 箭头函数
2.6 箭头函数
- 箭头函数不会绑定 this、arguments 属性
- 箭头函数不能作为构造函数来使用(不能和 new 一起来使用,会抛出错误),没有原型
// 1.之前的方式
function foo1() { }
var foo2 = function (name, age) {
console.log("函数体代码", this, arguments)
console.log(name, age)
}
// 2.箭头函数完整写法
var foo3 = (name, age) => {
console.log("箭头函数的函数体")
console.log(name, age)
}
// 3.箭头函数的练习
// 3.1. forEach
var names = ["abc", "cba", "nba"]
names.forEach(function (item) {
console.log(item);
})
names.forEach((item, index, arr) => {
console.log(item, index, arr)
})
// 3.2. setTimeout
setTimeout(function () {
console.log("settimeout");
}, 3000)
setTimeout(() => {
console.log("setTimeout")
}, 3000)
-
编写优化
-
如果只有一个参数 ,() 可以省略
-
如果函数体中只有一行执行代码,{}可以省略
-
一行代码中不能带return关键字,如果省略{},return一起省
-
并且这行代码的表达式结果返回值会作为整个函数的返回值
-
只有一行代码时候,运行代码的表达式结果会作为函数的返回值默认返回
-
如果默认返回值是一个对象,那么这个对象必须加**({对象})**
-
var names = ["abc", "cba", "nba"]
var nums = [20, 30, 11, 15, 111]
// 1.优化一: 如果箭头函数只有一个参数, 那么()可以省略
// names.forEach(item => {
// console.log(item)
// })
// var newNums = nums.filter(item => {
// return item % 2 === 0
// })
// 2.优化二: 如果函数体中只有一行执行代码, 那么{}可以省略
// names.forEach(item => console.log(item))
// 一行代码中不能带return关键字, 如果省略{}, 需要带return一起省略(下一条规则)
// var newNums = nums.filter(item => {
// return item % 2 === 0
// })
// 3.优化三: 只有一行代码时, 这行代码的表达式结果会作为函数的返回值默认返回的
// var newNums = nums.filter(item => item % 2 === 0)
// var newNums = nums.filter(item => item % 2 === 0)
// 4.优化四: 如果默认返回值是一个对象, 那么这个对象必须加()
// 注意: 在react中会经常使用 redux
// var arrFn = () => ["abc", "cba"]
// var arrFn = () => {} // 注意: 这里是{}执行体
// var arrFn = () => {name: "iii" }
i
// var arrFn = () => ({ name: "iii" })
// console.log(arrFn())
// 箭头函数实现nums的所有偶数平方的和
var nums = [20, 30, 11, 15, 111]
var result = nums.filter(item => item % 2 === 0)
.map(item => item * item)
.reduce((prevValue, item) => prevValue + item)
console.log(result)
- this规则之外, 箭头函数不绑定 this ,遵循变量查找规则,在作用域(函数)中找
- 也不能通过 apply、call 绑定去找
- 一层一层往上面查找
- 对象的** {} 不是作用域,只有函数作用域和全局作用域**
// 1.普通函数中是有this的标识符
// function foo() {
// console.log("foo", this)
// }
// foo()
// foo.apply("aaa")
// 2.箭头函数中, 压根没有this
// var bar = () => {
// console.log("bar:", this)
// }
// bar() window
// 通过apply调用时, 也是没有this,还是从上层作用域中找
// bar.apply("aaaa")
// console.log("全局this:", this)
// var message = "global message"
// 3.this的查找规则
var obj = {
name: "obj",
foo: function(){
var bar = () => {
console.log("bar:", this)
}
return bar
}
}
var fn = obj.foo() //fn 指向 bar 函数
fn.apply("bbb") //没有this,绑过来有什么用,要this?去上层作用域,foo函数,谁调用啊?obj!!!
// var obj = {
// name: "obj",
// foo: () => {
// var bar = () => {
// console.log("bar:", this)
// }
// return bar
// }
// }
// var fn = obj.foo()
// fn.apply("bbb") window
- 箭头函数中 this 应用场景:网络请求
- 异步:代码不会按照顺序一点一点执行
// 网络请求的工具函数
function request(url, callbackFn) {
var results = ["abc", "gds", "sws"]
callbackFn(results)//在这里调用默认是this
}
// 实际操作的位置
var obj = {
name: [],
network: function () {
// // 1. 早期
// var _this=this
// request("/songs/love",function(res){
// // console.log(res);//拿到results放在name里面
// _this.name=[].concat(res)
// })
// 2. 箭头函数
request("/name", (res) => {
this.name = [].concat(res)
})
}
}
obj.network()
console.log(obj.name);
三、题海战术(持续更新)
3.1 题目
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // 绑定: 默认绑定, window -> window
person.sayName(); // 绑定: 隐式绑定, person -> person
(person.sayName)(); // 虽然加了小括号,但是什么都没有做,标识符的优先级并没有改变。点语法的优先级很高。绑定: 隐式绑定, person -> person
(b = person.sayName)(); // 术语: 间接函数引用,得到独立的函数,进行调用 window -> window
}
sayName();
var name = 'window'
// {} -> 对象
// {} -> 代码块
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
// 箭头函数不绑定 this
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
// console.log(this) // 第一个表达式this -> person1
// console.log(this) // 第二个表达式this -> person2
// console.log(this) // 第三个表达式this -> person1
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
// 作用域:函数 和 全局
// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2
// 箭头函数隐式绑定没有意义,上层作用域
person1.foo2(); // 上层作用域: window
// 也不能使用 call 进行调用
person1.foo2.call(person2); // 上层作用域: window
// 拿到这个函数直接进行调用
person1.foo3()(); // 默认绑定: window
// 显式绑定到 person2进行调用,返回值是一个函数独立调用
person1.foo3.call(person2)(); // 默认绑定: window
// 拿到返回值显式绑定 person2
person1.foo3().call(person2); // 显式绑定: person2
// 隐式绑定person1,箭头函数去上层函数找,也是person1
person1.foo4()(); // person1
// 显式绑定 person2,箭头函数去上层作用域找,也是 person2
person1.foo4.call(person2)(); // person2
// 隐式绑定person1,箭头函数不能通过 call 去找
person1.foo4().call(person2); // person1
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
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)
}
}
}
// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')
// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2
// 箭头函数都是去上层作用域找
person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: person1
// 返回一个函数直接进行调用
person1.foo3()() // 默认绑定: window
// foo3是person2进行调用,但是返回的是一个普通函数,还是进行默认调用的
person1.foo3.call(person2)() // 默认绑定: window
// 拿到返回值进行显式绑定
person1.foo3().call(person2) // 显式绑定: person2
person1.foo4()() // 上层作用域查找: person1(隐式绑定)
person1.foo4.call(person2)() // 上层作用域查找: person2(显式绑定)
// 箭头函数不能通过 call 进行调用
person1.foo4().call(person2) // 上层作用域查找: person1(隐式绑定)
var name = 'window'
// 通过 new 进行调用
/*
1.创建一个空的对象
2.将这个空的对象赋值给 this (对this进行操作就是在操作这个对象)
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
// person 里面放了一个 obj 对象
this.obj = {
// 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
// 返回新的函数显式绑定 person2
person1.obj.foo1().call(person2) // 显式绑定: person2
// 返回的是箭头函数,foo2是通过obj进行调用的
person1.obj.foo2()() // 上层作用域查找: obj(隐式绑定)
// 返回的是箭头函数,foo2 是通过显式绑定的
person1.obj.foo2.call(person2)() // 上层作用域查找: person2(显式绑定)
// 箭头函数通过 call 调用没有意义
person1.obj.foo2().call(person2) // 上层作用域查找: obj(隐式绑定)
3.2 总结
- 显式绑定传入 null/undefined , 那么使用的规则是默认绑定
- 默认绑定 this 指向调用的对象
- 立即执行匿名函数有自己的作用域,变量没找到会沿着作用域链往上查找,this 指向全局对象
- 重点:看谁调用,不要和变量查找题搞混了