JavaScript 中的 this
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 不能在执行期间被赋值,并且在每次函数被 调用时 this 的值也可能会不同。
确定 this 的值
在非严格模式下,总是指向一个对象。但是在严格模式下,可以指定任意值。
开启严格模式的两种方法:
- 在整个脚本顶部开启
- 在函数顶部开启
1 2 3 4 5 6
| 'use strict' function func() { 'use strict' }
|
然后就可以根据不同的模式来确认this指向啦,
- 全局执行环境中,指向全局对象(非严格模式、严格模式)
- 函数内部,取决于函数被调用的方式
- 直接调用的this值:
- 非严格模式:全局对象(window)
- 严格模式:undefined
- 对象方法调用的this值:
- 调用者
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
|
console.log(this)
'use strict' console.log(this)
function func() { console.log(this) } func()
function func() { 'use strict' console.log(this) } func()
const fans = { name: 'ikun', sing() { 'use strict' console.log(this) } } fans.sing()
|
改变 this 的值
指定 this 的方法有两类
- 调用函数传入具体的 this:
call(参数1, 其他参数)
- 参数1:this 值
- 其他参数:传递给函数的其他参数,可以有很多个
apply(参数1, [参数2])
- 参数1:this 值
- 参数2:以数组形式给函数传递参数
- 创建绑定 this 的函数:
- bind()
- 和 call 类似,但是不执行函数
- 会返回一个绑定了 this 值的新函数
- 箭头函数:最近的 this 值是哪个,就是哪个
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
| function func(p1, p2) { console.log(this) console.log(p1, p2) } const person = { name: 'Alien' }
func.call(person, 1, 2)
func.apply(person, [3, 4])
const bindFunc = func.bind(person, 123) bindFunc(456)
const student = { name: 'ikun', sayThis: function() { console.log(this) } sayThat: () => { console.log(this) } } student.sayThis() student.sayThat()
|
重写 call() 方法
实现了一个自定义的 myCall
方法,能够在任意函数上调用,并且可以指定函数内部 this
的指向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Function.prototype.myCall = function(thisArg, ...args) { const f = Symbol() thisArg[f] = this const res = thisArg[f](...args) delete thisArg[f] return res }
const person = { name: 'Alien' } function func(num1, num2) { console.log(this) console.log(num1, num2) return num1 + num2 } const res = func.myCall(person, 5, 7) console.log(res);
|
- 在
Function
的原型对象上添加 myCall()
方法,保证所有函数都可以调用
- 方法内部通过动态为对象添加方法来指定 this 的指向
- 调用完毕后再删除
delete()
方法
- Symbol函数的目的是避免重名导致的错误,每次 symbol 的返回值都是唯一的
thisArg[f]
使用了 Symbol 作为属性名,正常情况下不会影响原函数或覆盖,而 thisArg.f
直接使用了字符串作为属性名,遇到同名方法可能会覆盖
重写 apply() 方法
实现自定义函数 myApply() ,与 myCall 方法类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Function.prototype.myApply = function (thisArg, args) { const f = Symbol() thisArg[f] = this const res = thisArg[f](...args) delete thisArg[f] return res } const person = { name: 'Alien' } function func(num1, num2) { console.log(this) console.log(num1, num2) return num1 + num2 } const res = func.myApply(person, [5, 7]) console.log(res);
|
- 与
myCall()
的写法一致,只需要将传进来的参数定为数组即可
重写 bind() 方法
实现自定义函数 myBind()
,需要实现新函数参数的拼接
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, ...args) { return (...args2) => { return this.myCall(thisArg, ...args, ...args2) } } const person = { name: 'Alien' } function func(num1, num2, num3, num4) { console.log(this) console.log(num1, num2, num3, num4) return num1 + num2 + num3 + num4 } const newBind = func.myBind(person, 5, 7) const res = newBind(1, 4) console.log(res);
|
function
原型上添加myBind
函数,参数1为绑定的this,参数2为绑定的参数
- 内部返回一个新箭头函数,目的是绑定作用域中的this
- 返回的函数内部,通过
call
进行this和参数绑定
- 通过
call
的参数2和参数3指定绑定的参数,和调用时传递的参数
JS 继承
ES5-原型链继承
- 将父类的实例作为子类的原型实现继承
- 这种继承方法的缺点是父类中的引用类型数据会被所有子类共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Parent(name) { this.name = name this.foods = ['西蓝花', '西红柿'] this.sayFoods = function () { console.log(this.foods) } }
function Son() { }
Son.prototype = new Parent('jack') const s1 = new Son() s1.sayFoods()
const s2 = new Son() s2.sayFoods()
s2.foods.push('西葫芦')
s2.sayFoods() s1.sayFoods()
|
ES5-构造函数继承
- 在子类的构造函数中通过
call
或者apply
调用父类的构造函数
- 这种继承方法的缺点是:子类没法使用父类原型上的属性/方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Parent(name) { this.name = name } Parent.prototype.sayHi = function () { console.log('你好,我叫:', this.name) }
function Son(name) { Parent.call(this, name) }
const s1 = new Son('lucy') const s2 = new Son('rose') s1.sayHi()
|
ES5-组合继承
- 组合继承的核心步骤有2步:
- 通过原型链继承公共的属性和方法
- 通过构造函数继承实例独有的属性和方法
- 组合继承的特点:调用2次父类的构造函数,浪费性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function Person(name) { this.name = name }
Person.prototype.sayHi = function () { console.log(`你好,我叫${this.name}`) }
function Student(name, age) { Person.call(this, name) this.age = age }
Student.prototype = new Person()
const s = new Student('李雷', 18)
|
ES5-原型式继承
- 原型式继承的核心步骤是:对某个对象进行浅拷贝,可以通过内置api
Object.create()
实现,不需要调用构造函数即可实现继承,主要针对于继承对象的情况
- 原型式继承的缺点是:父类中的引用数据类型,会被所有子类共享
1 2 3 4 5 6 7 8 9 10 11 12
| const parent = { name: 'parent', age: 25, friend: ['rose', 'ice', 'robot'], sayHi() { console.log(this.name, this.age) } } const son1 = Object.create(parent) const son2 = Object.create(parent) son1.friend.push('lucy') console.log(son2.friend)
|
ES5-寄生式继承
- 寄生式继承的核心步骤是:基于对象创建新对象(可以使用
Object.create
),并且为新创建的对象增加新的属性和方法
- 寄生式继承和上一节学习的原型式继承的区别是:创建出来的新对象,会额外的增加新的属性/方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function createAnother(origin) { const clone = Object.create(origin) clone.sayHi = function () { console.log('你好') } return clone } const parent = { name: 'parent', foods: ['西蓝花', '炒蛋', '花菜'] } const son1 = createAnother(parent) const son2 = createAnother(parent)
|
ES5-寄生组合式继承
- 寄生组合式继承的核心步骤是:通过构造函数来继承属性,通过原型链来继承方法
- 寄生组合式继承和组合式继承的区别是:原型链的继承并没有调用父类的构造函数,而是直接基于父类的原型创建一个新副本实现继承
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 31 32 33
| function inheritPrototype(son, parent){ const prototype = Object.create(parent.prototype, { constructor: { value: son } }) son.prototype = prototype }
function Parent(name) { this.name = name this.foods = ['西蓝花', '西葫芦', '西红柿'] }
Parent.prototype.sayHi = function () { console.log(this.name, `我喜欢吃,${this.foods}`) }
function Son(name, age) { Parent.call(this, name) this.age = age }
inheritPrototype(Son,Parent)
Son.prototype.sayAge = function () { console.log('我的年龄是', this.age) } const son1 = new Son('jack', 18) const son2 = new Son('rose', 16)
|
ES6-class实现继承
class 核心语法:
- 如何定义及使用类:
- 如何定义实例属性/方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Person { name food constructor(name, food) { this.name = name this.food = food } sayHi() { console.log(`你好,我叫${this.name},我喜欢吃${this.food}`) } } const p = new Person('小黑', '西蓝花') p.sayHi()
|
- 通过
class 类名{}
的形式来定义类
- 内部直接写实例属性,可以设置默认值
- 实例方法的添加方式为
方法名(){}
- 构造函数通过
constructor
进行添加
- 通过
new 类名()
创建实例,会调用构造函数constructor
class 实现继承:
- 子类通过
extends
继承继承父类
- 子类如果需要重新定义构造函数,必须在内部通过
super
关键字调用父类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Student extends Person { song constructor(name, food, song) { super(name, food) this.song = song } sing() { console.log(`我叫${this.name},我喜欢唱${this.song}`) } } const s = new Student('李雷', '花菜', '孤勇者') s.sayHi() s.sing()
|
class 私有、静态属性和方法:
- class中私有属性/方法
- 定义和使用时需要使用关键字
#
- 私有属性只能在类的内部使用,外部无法使用(代码中)
- Chrome的控制台中为了方便调试,可以直接访问
- class中静态属性/方法
- 定义和使用时需要使用关键字
static
- 通过类访问
- 静态方法中的
this
是类
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 31 32
| class Person { constructor(name) { this.name = name } #secret = '我有一个小秘密,就不告诉你' #say() { console.log('私有的say方法') } info() { console.log(this.#secret) this.#say() } static staticMethod() { console.log('这是一个静态方法') console.log(this) } static info = '直立行走,双手双脚' } const p = new Person('jack') console.log(p)
console.log(p['#secret']) p.info()
Person.staticMethod() console.log(Person.info)
|
fetch
AJAX & axios & fetch的关系:
AJAX
:AJAX
是一种基于原生 JavaScript 的异步请求技术。它使用 XMLHttpRequest
对象来发送请求和接收响应。
axios
:axios
是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node.js 中使用。它提供了更高级别的封装,使发送请求和处理响应更加简单和灵活。
fetch
:fetch
是浏览器内置的 API,用于发送网络请求。它提供了一种现代化、基于 Promise 的方式来进行网络通信。用法和axios
类似,但相比于 axios
,它的功能和封装级别更为简单。
fetch 核心语法
fetch
函数的参数:
- 参数1:请求的url地址
- 参数2:以对象的形式设置请求相关的内容比如,方法,请求头,提交的数据等。
fetch
获取到响应结果,需要如何解析:
1 2 3 4
| fetch(资源地址,{...配置项对象}) .then(response=>{ })
|
GET请求
fetch
发送get请求时,不需要设置请求方法,因为默认的就是get
URLSearchParams
可以用来创建或者解析查询字符串,这里通过它将对象转为查询字符串
1 2 3 4 5 6 7 8 9
| document.querySelector('#btn').addEventListener('click', async () => { const p = new URLSearchParams({ pname: '广东省', cname: '广州市' }) const res = await fetch('http://hmajax.itheima.net/api/area?' + p.toString()) const data = await res.json() console.log(data); })
|
POST请求-提交JSON
fetch
函数的第二个参数可以设置请求头,请求方法,请求体,根据接口文档设置对应的内容即可
- 可以通过
JSON.stringify
将对象转为JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| document.querySelector('#btn').addEventListener('click', async function () { const headers = new Headers() headers.append('content-type', 'application/json') const res = await fetch('http://hmajax.itheima.net/api/register', { method: 'post', headers, body: JSON.stringify({ username: 'itheima9876', password: '123456' }) }) const json = await res.json() console.log(json) })
|
POST请求-提交FormData
fetch
提交FormData
时不需要额外的设置请求头
- 实例化
FormData
之后可以通过append(key,value)
方法来动态添加数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <input type="file" class="file" accept="image/*"> <script> document.querySelector('.file').addEventListener('change', async function (e) { const data = new FormData() data.append('img', this.files[0]) const res = await fetch('http://hmajax.itheima.net/api/uploadimg', { method: 'post', body: data }) const json = await res.json() console.log(json) }) </script>
|
Generator
Generator
对象由生成器函数返回并且它符合可迭代协议和迭代器协议。他可以用来控制流程,语法行为和之前学习的函数不一样
- 可以通过生成器函数(
function* xxx(){}
)来生成Generator
对象:
- 通过
Generator
对象的next
方法可以获取yield
表达式之后的结果
- 通过
for of
获取每一个yield
的值
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
| function* foo() { yield 'a' yield 'b' yield 'c' return 'd' }
const f = foo()
const res1 = f.next() console.log(res1) const res2 = f.next() console.log(res2) const res3 = f.next() console.log(res3)
const res4 = f.next() console.log(res4)
const res5 = f.next() console.log(res5)
const f2 = foo() for (const iterator of f2) { console.log(iterator) }
|
generator 实现id生成器
- 定义生成器函数
- 内部使用循环,通过
yield
返回id
并累加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function* generator() { let id = 0 while (true) { yield id++ } }
const idMaker = generator()
const { value: id1 } = idMaker.next() console.log(id1) const { value: id2 } = idMaker.next() console.log(id2)
|
generator-管理异步
- 使用
Generator
控制流程的本质是利用yield
关键字来分隔逻辑比如示例中依次调用了多个接口,通过yield
分隔,通过next
来触发调用
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
| <button class="getWeather">天气查询</button> <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js"></script> <script>
function* weatherGenerator() { yield axios('http://hmajax.itheima.net/api/weather?city=110100') yield axios('http://hmajax.itheima.net/api/weather?city=310100') yield axios('http://hmajax.itheima.net/api/weather?city=440100') yield axios('http://hmajax.itheima.net/api/weather?city=440300') }
const cityWeather = weatherGenerator() document.querySelector('.getWeather').addEventListener('click', async () => { await cityWeather.next().value.then(res => { console.log(res.data.data); }) }) </script>
|
点击一次就获取一次,实现对异步的控制