1.小程序的样式需要多手机适配
设置了一个 rpx 的高度或者宽度,要试一下在不同手机上的显示效果是否合适
一般来说苹果 12/13会比较容易压缩高度,苹果6/7/8也是,要重点检查此类机型
例子:在微信小程序中实现高度样式的多手机适配,可以从以下几个方面考虑
使用百分比单位或相对单位:避免使用固定像素单位(如px),而是使用相对单位(如百分比、rpx)。例如,使用
height: 100%
或height: 100rpx
,可以相对于父容器或屏幕高度来设置高度。媒体查询(Media Queries):通过使用媒体查询,可以根据屏幕的宽度或高度应用不同的样式。使用
wxss
文件中的@media
规则,根据不同的屏幕宽度或高度设置不同的样式。例如,以下示例将在屏幕宽度小于500px时应用不同的样式:屏幕小于 500px 的时候,把容器的高度缩小为 200px
css@media screen and (max-width: 500px) { .my-element { height: 200rpx; } }
动态计算样式:使用 JavaScript 在运行时动态计算样式。可以在
onLoad
或onReady
生命周期函数中获取屏幕高度,然后根据计算结果设置元素的样式。例如,以下示例将在页面加载时动态计算屏幕高度的一半,并将其应用到元素的高度:
jsonLoad: function () { const screenHeight = wx.getSystemInfoSync().windowHeight; const halfHeight = screenHeight / 2; this.setData({ elementHeight: `${halfHeight}px` }); }
html<view style="height: {{elementHeight}};"></view>
使用
<scroll-view>
组件:如果内容超出屏幕高度,可以将内容放在<scroll-view>
组件中,以实现纵向滚动。(类似于 vue 的滚动容器)例如,以下示例将
<scroll-view>
组件的高度设置为屏幕高度的70%:htmlCopy code <scroll-view style="height: 70vh;"> <!-- 内容 --> </scroll-view>
通过结合使用上述方法,你可以实现微信小程序的高度样式在不同手机上的适配。注意,不同手机的屏幕尺寸和比例可能有所不同,因此建议进行测试和调整以获得最佳的适配效果。
2..listItem>view:nth-child(2n+2)
这是一个 CSS 选择器规则,用于选择 .listItem
下的子元素 <view>
中,索引为偶数(2n+2)的元素。
具体解释如下:
.listItem>
:选择具有.listItem
类的元素的直接子元素。view
:选择标签名为<view>
的元素。:nth-child(2n+2)
:选择索引为偶数(2n+2)的子元素。
在这个选择器规则中,索引是从 1 开始计数的。所以 2n+2
表达式中的 n
代表任意非负整数(0、1、2….)。
例如,假设有以下 HTML 结构:
<div class="listItem">
<view>Item 1</view>
<view>Item 2</view>
<view>Item 3</view>
<view>Item 4</view>
<view>Item 5</view>
<view>Item 6</view>
</div>
根据 .listItem>view:nth-child(2n+2)
的选择器规则,将会选择 .listItem
下的索引为偶数的子元素 <view>
,也就是 Item 2
、Item 4
和 Item 6
。
你可以将特定的样式应用于这些选中的元素,例如改变它们的背景色:
.listItem>view:nth-child(2n+2) {
background-color: yellow;
}
这样,选中的元素会以黄色背景显示。
请注意,这只是 CSS 选择器的一种使用方式,实际使用时要根据具体情况和需求进行调整。
3.wx:if的注意事项
wx:if=“”这一定要有,否则取不到 js 的值,就相当于是 string,那么就一定是真的,这是一个坑

4.有的vite项目要修改 host
修改为0.0.0.0时,打开页面一直刷新
修改为自己的无线网的 ip 地址:


解决!
5.v-model.trim
v-model.trim="search_key"
是 Vue.js 中的指令,用于在表单输入元素(如 <input>
或 <textarea>
)上实现双向数据绑定,并自动去除输入内容的首尾空格。
与v-model.trim类似的还有哪些
.number
:将输入值转换为数值类型。适用于处理数字输入。htmlCopy code <input type="number" v-model.number="quantity">
.lazy
:在输入框失去焦点或按下回车键时才更新数据。适用于延迟更新输入数据。htmlCopy code <input type="text" v-model.lazy="username">
.trim
:自动去除输入内容的首尾空格。htmlCopy code <input type="text" v-model.trim="name">
.debounce
:延迟更新数据,在输入结束后一段时间再触发更新。需要配合v-model
和@input
事件一起使用。htmlCopy code <input type="text" v-model="searchQuery" @input.debounce="search">
.prevent
:阻止默认的表单提交行为。适用于在表单提交时执行自定义的操作。htmlCopy code <form @submit.prevent="submitForm"> <!-- 表单元素 --> </form>
6.微信小程序实现触底加载分页
不需要使用什么插件,直接使用小程序自带的触底钩子:onReachBottom
data:{
rankList:[],
lastSortKey:"",
pageSize:10,
isBottom:false
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
// 触底加载逻辑处理
// 请求新数据或加载更多数据
if(!this.data.isBottom){
this.getRankList();
}
},
getRankList()
{
// console.log(111)
Common.postRequest(Common.urls.agentRank,
{
param: {
"custAgentType": parseInt(this.data.custAgentType),
"year": parseInt(this.data.year),
"month":parseInt(this.data.month),
"pageSize": this.data.pageSize, //页大小默认20
"lastSortKey": this.data.lastSortKey, //分页用
}
}
).then((res) => {
console.log(res);
if (res.data) {
//在数据列表中本来有数据并且新来的数据长度为 0 时,就可以设置 isBottom 为 true
if(this.data.rankList.length!==0 && res.data.rankList.length==0){
// console.log(res.data.rankList)
console.log("长度为 0")
this.setData({
isBottom:true
})
}else{
this.setData({
rankList:this.data.rankList.concat(res.data.rankList),
updateTime:res.data.lastUpdateTime,
lastSortKey:res.data.rankList[res.data.rankList.length-1].sortKey
})
}
}
})
},
resetStatus(){
this.setData({
isBottom:false,
lastSortKey:"",
rankList:[]
})
},
非常需要注意的地方:触底加载实现分页,在切换不同选项(月份,类别)的时候(也就是重新搜索请求数据的时候),一定要重置状态,把isBottom、dataList、lastSortKey(pageIndex)进行重置,调用resetStatus方法
同时:如果页面涉及了不同的排序方式,并且不同的排序方式对应不同的 lastSortKey 的花话,那么必须要根据当前状态重设一下lastSortKey,因为lastSortKey很可能并不是初始值了

7.v-cloak防止闪屏
[v-cloak]
是一个 Vue.js 指令,用于解决在页面加载时,Vue 实例渲染之前,显示模板代码的问题。它通常与 CSS 一起使用。
这个指令的常见用法是将其应用于包含 Vue 模板的元素或组件,然后使用相应的 CSS 规则来隐藏该元素或组件,直到 Vue 实例完成渲染。
在你提供的样式中,[v-cloak]
是一个属性选择器,它会选择具有 v-cloak
属性的元素。然后,display: none;
规则将被应用于这些元素,使其在页面加载时隐藏。
示例代码如下:
App.vue 一般把v-cloak写在这里,全局适用
<style>
[v-cloak] {
display: none;
}
</style>
<div id="app">
<div v-cloak>
<!-- Vue 模板 -->
</div>
</div>
<script>
export default {
}
</script>
作用:使用 [v-cloak]
可以避免在 Vue 实例加载之前显示未渲染的模板代码,从而提供更好的用户体验。
8.工作中最重要的三点
心态
执行力
沟通
四大环境:开发,测试,预发,生产
微软编辑器嵌入 gpt4:
发邮件的发送人模版
收件人:fe@songshuai.com, wangligui@songshuai.com, wanglijing@songshuai.com, test@songshuai.com, yinjianzhen@songshuai.com(产品), xiewenjing@songshuai.com(UI), xiongxuefei@songshuai.com
抄送:liuxiaojun@songshuai.com(产品老大), liuhaitao@songshuai.com
9.<transition>
过渡组件
<transition>
元素是 Vue 的过渡组件,用于在元素进入或离开 DOM 时添加动画效果。下面是 <transition>
元素的基本使用方法:
使用
<transition>
元素包裹需要添加过渡效果的元素或组件,可以使用name
属性指定过渡效果的名称。这个名称将在 CSS 样式中定义过渡效果的类名。html<transition name="fade"> <!-- 要添加过渡效果的元素或组件 --> </transition>
在 CSS 样式中,通过定义过渡效果的类名和过渡相关的样式来实现具体的动画效果。
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s; //作用是在元素的透明度发生变化时,以持续时间为 0.5 秒的过渡效果进行平滑过渡,其实opacity我们一般写为 all,就是所有的变化都 0.5s了
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active
和.fade-leave-active
类名用于定义过渡效果的持续时间和过渡属性。.fade-enter
和.fade-leave-to
类名用于定义元素进入和离开时的起始和结束状态。
10.接口签名的传送策略
接口说明:
签名key = eWnIx3SCTRopyYi2
时间戳 time = 当前时间戳(秒)
签名方法 sign = md5(key+time)
在封装请求的文件中统一设置签名参数:request.js
const md5 =require('md5');
function getCoinSign(time) {
const key = 'eWnIx3SCTRopyYi2'
return md5(key+time)
}
requestHeader = wx.getStorageSync("requestHeader") || {} //wx.getStorageSync() 是微信小程序提供的同步获取本地缓存数据的方法
// post请求封装
function post(url, data, showLoading = true) {
log.request('Post url:', url, 'params:', data)
let time = parseInt(Date.now() / 1000); //因为需要的是秒而不是毫秒,所以要除以 1000
data = Object.assign({}, data, commonParams) //commonParams是一些公共的参数
data.time = time;
data.sign = getCoinSign(time) //根据时间戳得到签名
var postRequest = handlePromisify(wx.request, showLoading)
setCommonParams() //设置公共的参数
return postRequest({
url: url,
method: 'POST',
data: data,
header: requestHeader,
})
}
11.打 tag 送测
生成 tag 链接,送测
一个 tag 也相当于一个版本,每当测试反馈错误改正之后,就要新建一个 tag 然后再交给测试
tag的就是一个存档点,好处就是可以记录版本,我们后面可以接着提交新的,但是测试还是可以拉取之前的对应的 tag 版本!
拉取tag
tag 相当于是一个仓库的某个分支的存档点,仓库>分支>tag
1.要拉取某个 Git 仓库的特定标签版本,可以使用以下命令:
git clone --branch <tag> <repository_url>
其中 <tag>
是要拉取的标签名称,<repository_url>
是 Git 仓库的 URL。
2.如果你已经克隆了仓库,并且只想获取特定标签版本的更新,可以使用以下命令:
git checkout <tag>
这将切换到指定的标签版本,使你能够查看和使用该版本的代码。
12.小程序体验码送测
首先上传代码
选择版本升级
其次登录微信开发者平台,可以看到我们上传的版本代码,还可以设置为体验版,体验版会生成体验二维码
这个二维码交给测试即可
注意事项:
小程序上传代码之前,一定要把所有的环境配置都设置好,因为小程序是所有代码全部上传的,不同于 git 可以选择我们想要上传的代码进行上传,尤其是想要上传预发环境的代码,一定要修改预发环境变量!
13.delete 运算符
delete
是 JavaScript 的一个运算符,用于删除对象的属性或数组的元素。
在对象中使用 delete
运算符时,它可以用来删除指定属性。
示例:
const obj = {
foo: 'bar',
baz: 'qux'
};
delete obj.foo; // 删除对象 obj 的属性 foo
console.log(obj); // 输出: { baz: 'qux' }
在数组中使用 delete
运算符时,它可以删除指定索引位置的元素,并将该位置的值设置为 undefined
,但数组的长度不会变化。
示例:
const arr = [1, 2, 3, 4, 5];
delete arr[2]; // 删除数组 arr 中索引为 2 的元素
console.log(arr); // 输出: [1, 2, undefined, 4, 5]
需要注意的是,使用 delete
运算符删除数组元素会导致数组的稀疏性(sparse array),即数组中存在 undefined
的空洞。因此,通常推荐使用数组的 splice()
方法来删除元素,以保持数组的连续性。
14.js 懒运算
逻辑与运算符 &&
和逻辑或运算符 ||
都是懒运算
15.js 原型与原型链的总结和理解
讲原型的时候,我们应该先要记住以下几个要点,这几个要点是理解原型的关键:
1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
2、所有的引用类型都有一个’_ _ proto_ _'属性(也叫隐式原型,它是一个普通的对象)。
3、所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。
4、所有引用类型,它的’_ _ proto_ _'属性指向它的构造函数的’prototype’属性。
5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。(并且是一层一层不断地寻找,这涉及原型链)
图解:
obj/fun/arr.__proto__
——> Fun.prototype
注意__proto__
和prototype
属性都是一个对象!就是原型!
例子:
//这是一个构造函数
function Foo(name,age){
this.name=name;
this.age=age;
}
/*根据要点3,所有的函数都有一个prototype属性,这个属性是一个对象
再根据要点1,所有的对象可以自由扩展属性
于是就有了以下写法*/
Foo.prototype={
// prototype对象里面又有其他的属性
showName:function(){
console.log("I'm "+this.name);//this是什么要看执行的时候谁调用了这个函数
},
showAge:function(){
console.log("And I'm "+this.age);//this是什么要看执行的时候谁调用了这个函数
}
}
var fn=new Foo('小明',19)
/*当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它
构造函数的'prototype'属性中去找*/
fn.showName(); //I'm 小明
fn.showAge(); //And I'm 19
为什么要有原型?
原型相当于是一个仓库,将所有new 出来的对象的公共部分(共有资源)存储起来
这样对象本身不需要携带这些公共部分,但是却可以访问和使用到,节省了不少的资源。
就比如上面,我们 new 出来的 fn 对象本身没有那两个方法,但是却可以通过原型使用到,这其实就类似于 array对象,可以去使用它的原型__proto__
中的方法(也就是 Array 构造函数的 prototype
中的方法)
那原型链又是什么呢?
首先要明确:
因为隐式原型和显示原型本身也都是对象,那么它们也有__proto__
属性,并且这个属性是一个对象,所以它的__proto__
属性一定指向Object 构造函数的prototype
属性。
引例:
// 构造函数
function Foo(name,age){
this.name=name;
this.age=age;
}
Object.prototype.toString=function(){
//this是什么要看执行的时候谁调用了这个函数。
console.log("I'm "+this.name+" And I'm "+this.age);
}
var fn=new Foo('小明',19);
fn.toString(); //I'm 小明 And I'm 19
console.log(fn.toString===Foo.prototype.__proto__.toString); //true
console.log(fn.__proto__ ===Foo.prototype)//true
console.log(Foo.prototype.__proto__===Object.prototype)//true
console.log(Object.prototype.__proto__===null)//true
理解:
通过上面的代码,我们知道这个toString()方法是在Object.prototype里面的,当调用这个对象的本身并不存在的方法时,它会一层一层地往上面的 __proto__
去找,一直到null为止。
所以当fn调用toString()时,JS发现fn中没有这个方法,于是它就去Foo.prototype中去找,发现还是没有这个方法,然后就去Object.prototype中去找,找到了,就调用Object.prototype中的toString()方法。
这就是原型链,一个对象能够调用Object.prototype中的方法正是因为存在原型链的机制。
原型链的存在,使那些顶级的所有对象共享的方法可以储存在 Object.prototype属性这个大仓库里面!进一步形成了统一,节省了资源。
注意事项:在使用原型的时候,一般推荐将需要扩展的方法写在构造函数的prototype属性中,避免写在_ _ proto_ _属性里面。
通过在构造函数的 prototype
属性上添加方法,可以将方法作为共享的方法添加到所有实例的原型链中。由于对象的 __proto__
属性等价于构造函数的 prototype
属性(完全相等),所以说其实在_ _ proto_ _属性里面写也是可以的,也可以通过原型链被其他对象所继承。
function Fun() {}
var obj = new Fun();
console.log(obj.__proto__ === Fun.prototype); // true
例如,考虑以下示例:
function Person(name) {
this.name = name;
}
var person1 = new Person('Alice');
console.log(person1.__proto__ === Person.prototype); // true
person1.__proto__.sayHello = function() {
console.log('Hello!');
};
person1.sayHello(); // Hello!
var person2 = new Person('Bob');
person2.sayHello(); // Hello!
在上述示例中,我们通过对象 person1
的 __proto__
属性在原型对象上定义了 sayHello
方法。由于 person1.__proto__
和 Person.prototype
指向同一个原型对象,因此 sayHello
方法也可以通过 person2
实例的原型链访问并被调用。
但是为什么不建议写在
_ _ proto_ _
属性里面呢?
需要注意的是,尽管在某些 JavaScript 引擎中支持在 __proto__
属性上定义方法,并且可以通过原型链被继承,但这是一个不太标准的行为。规范中推荐使用构造函数的 prototype
属性来定义方法,以确保方法可以被所有实例对象共享和继承。
所以,尽管对象的 __proto__
属性通常指向构造函数的 prototype
属性,但为了保持代码的可读性和可维护性,建议仍然将方法定义在构造函数的 prototype
属性上,以便明确表示这些方法是用于实例对象的共享和继承。
而__proto__
属性主要是用来操作对象的原型链的,用于获取和设置对象的原型:
可以通过 __proto__
属性来动态地修改对象的原型链,从而实现继承和属性的继承。通过修改 __proto__
属性,可以将对象的原型指向另一个对象,使对象可以继承该对象的属性和方法。
但是同样出于可读性和可维护性的考虑,推荐使用标准的方法来操作原型而不是使用__proto__
属性,如 Object.getPrototypeOf(obj)
和 Object.setPrototypeOf(obj, prototype)
。
16.使用class 和 extends 语法来实现对象方法的定义和继承
对于原型和原型链的变种:
之前原型的方法现在作为类里面定义的方法,类 = 构造函数 + 构造函数原型对象(类的原型对象,定义了公共的方法,同样不会跟随 new 而占用空间在每个对象),对象与类的原型对象形成关系,类的原型对象作为对象的原型,obj.__proto === Class.prototype
之前的原型链现在作为类和类之间的继承,子类的原型对象作为父类的实例,对象与类1与类2与类n它们的原型对象之间形成了一个原型链!
好处:结构更加清晰,代码更易维护
例子:
class Animal {
constructor(name) { //构造函数Animal
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
//speak() {
//console.log(`${this.name} barks.`);
//}
fetch() {
console.log(`${this.name} fetches a ball.`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // Output: Buddy makes a sound.
dog.fetch(); // Output: Buddy fetches a ball.
现代的 JavaScript 开发中,使用 类class 代替了构造函数 fun,这样更易读和易于维护,并且可以进行方法继承
类的原型对象可以通过访问类的 prototype
属性来获取。每当使用 class
关键字定义一个类时,JavaScript 引擎会自动创建一个原型对象,并将其赋值给类的 prototype
属性。
下面是一个示例来说明类的原型对象:
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
}
console.log(Person.prototype); // 输出:Person {}
console.log(Person.prototype.constructor === Person); // 输出:true,类的原型对象的constructor属性代表了构造函数
在上述示例中,Person.prototype
表示 Person
类的原型对象。该原型对象是一个普通的对象,它具有一个指向 Person
构造函数的 constructor
属性,并且类中定义的方法,如 sayHello
方法,会被添加到这个原型对象上。
通过原型对象,类的实例可以通过原型链访问并继承这些方法。例如,我们可以创建 Person
的实例,并通过原型链调用 sayHello
方法:
const person = new Person('John');
person.sayHello(); // 输出:Hello, my name is John.
总结起来,类的原型对象是一个普通的 JavaScript 对象,它由类的构造函数自动创建,并且类中定义的方法会被添加到这个原型对象上,以实现方法的共享和继承。
这里方法的共享和之前是一个原理,因为对象的__proto__
属性会指向类的原型对象(类.prototype),而类中的方法(这样书写方法更加清晰明确,这也是相比之前的一个好处)会被自动添加到类的原型对象中,所以所有 new 出来的对象都可以使用类中的方法,和之前大概相似。
而继承这一点其实类似于之前构造函数的原型链,只不过这里通过extends来实现,继承可以让子类的原型对象指向父类的原型对象(因为子类的原型对象作为了父类的实例,其实这里本质上还是原型链),这对于自己 new 出来的对象又是进一步的一种扩展,因为可以一层一层向上寻找。
extends 关键字的原理:
当使用 extends
关键字创建子类时,子类的原型对象会被设置为父类的实例。这样一来,子类通过原型链就可以访问并调用父类的方法。
内部原理:
Son.prototype = Object.create(Parent.prototype); // 将子类的原型对象设置为父类的实例
Son.prototype.constructor = Son; // 修复子类原型对象的构造函数指向
注意:Object.create()
是一个静态方法,用于创建一个新对象,并将该新对象的原型设置为指定的对象,即Parent.prototype。
这样Object.create(Parent.prototype)这个obj满足:obj.__proto__===Parent.prototype
,成功让子类的原型对象的__proto__
指向父类的prototype
。
也就是说,子类的原型对象通过 Object.create(Parent.prototype)
进行创建,覆盖了本来的子类的原型对象,其中 Parent.prototype
是父类的原型对象,这样子类的原型对象就成为了父类的一个实例。
某种意义上来说,其实 class 类和 funtion 构造函数是基本类似的!class 里面本身就包括了构造函数。
17.class 类的本质
ES6 之后,JavaScript 也引入了 class 关键字用于声明一个类。但需要注意的是,这样声明出来的类其实在底层还是使用了 JavaScript 的函数 和 原型链 (来模拟类的行为)。
例子:
class Person {
constructor (name) {
this.name = name
}
talk () {
console.log(`${this.name} says hello`)
}
}
const gery = new Person('Grey')
const mockDomButton = {} // 模拟一个DOM上的按钮对象
mockDomButton.onClick = gery.talk; // 绑定点击事件,把gery 实例上的方法绑定给 dom 对象作为一个属性,这样调用这个函数的对象就是 dom 对象了,
mockDomButton.onClick() // 输出的结果是 undefined says hello
说明的问题: talk 其实并不像 java 那样,它不是一个Person类内部封装的方法,而只是一个常规的JavaScript函数,赋值到了Person类的原型对象上而已。因此,talk 函数里的 this 对应的是调用时的上下文(也就是调用这个函数的对象,不一定是类的实例对象)而不是定义时的上下文(只有箭头函数是定义时的上下文),这点跟 Java 和 C++ 的差别很大,这也是 js 的 class 一个很大的缺点。
实际上talk 函数被绑定给 mockDomButton 对象,并作为一个属性,所以talk 函数的this 指向的是 mockDomButton 而不是 grey ,mockDomButton对象 并没有 name 属性于是 输出了 undefined says hello
解决这个问题的办法当然是有的,先介绍两个仍然使用 class 的方案: ——> 这两种方案在react 中都有重要应用,用于解决 class 组件的内部自定义函数在用于事件绑定、直接调用的时候, this 指向不为类而是 undefined的问题
- 方案一:使用函数的 bind 方法
bind() 方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数
修改 Person.js 文件如下
class Person {
constructor (name) {
this.name = name
this.talk = this.talk.bind(this); // 在构造器里显式调用 bind 函数绑定 this,这样方法里面就无论何时都可以访问类中的属性了(this 代表的就是实例对象了)
}
talk () {
console.log(`${this.name} says hello`)
}
}
再次运行上面的测试代码,这次的输出就是正确的了 —— Grey says hello
这种方案的缺点就是需要繁琐地写这种 bind 方法调用语句,当这个类的方法很多时,会显得构造器非常臃肿,降低可读性和编码效率如:

关于this.talk = this.talk.bind(this);的解释
this.talk = this.talk.bind(this);
的作用是将对象中的 talk
方法绑定到该对象的上下文(this
),这样 talk 的 this 就是该对象了。
在 JavaScript 中,函数的执行上下文(this
值)取决于函数被调用的方式。通过 bind()
方法可以显式地指定函数执行时的上下文。在这种情况下,this.talk.bind(this)
的目的是确保 talk
方法在任何情况下都使用当前对象作为其执行上下文,不会丢失或者改变 this 的引用。
- 方案二:使用类属性+箭头函数的方式来定义方法
虽然可以用使用箭头函数的方式定义 talk 函数来使 this 指向 class类,但是这时的箭头函数相当于放在了constructor里面,所以这个函数不会被绑定在 class的原型对象上,也就是说不会在原型链上,这又失去了原型链的特性,也不是一种推荐的方法。
class Person {
constructor(name) {
this.name = name
}
talk = () => {
console.log(`${this.name} says hello`)
}
}
这种语法是 ES2017 才引入的,它等效于:
class Person {
constructor(name) {
this.name = name
this.talk = () => {
console.log(`${this.name} says hello`)
}
}
}
能成功输出 Grey says hello
但是,这种方案也有缺点 —— 由于它等效于函数定义放在了构造器内,所以:
一、这个方法不在原型链上(不在类的原型对象上,失去了原型的特性),即 Person.prototype.talk 的值是undefined ,所以这个类的子类并不能使用 super.talk() 调用到父类这个方法,所以下面这段代码会报错
class Student extends Person {
talk = () => {
super.talk(); // 报错
console.log("student talk hi");
}
}
const student = new Student('Tom');
student.talk();
二、每次创建一个 Person 实例都会创建一个 talk 函数,造成性能浪费 (仅仅是用来与方案一对比)——>因为失去了原型的特性,没有原型这个仓库来储存公共方法了,这里就只是相当于 new 了一个什么都有的构造函数,当然会每个对象都创建一份
const Grey = new Person('Grey')
const Tom = new Person('Tom')
console.log(Grey.talk === Tom.talk); //输出 false
使用工厂函数解决,不用 class 类:
在 JavaScript 的现实场景中,尤其是前端代码,我们很少真正用到类继承,大多数时候,工厂函数就能完成我们的目标。
以React为例,官方这几年推崇 Hooks 的意图也很明显 —— 摆脱JavaScript class 带来的复杂性,拥抱函数式风格。
自己对于 class 类的理解:
其实 class 本质上还是构造函数,因为 class 中真正其作用的部分就是构造函数罢了,而class 的原型对象,应该就是构造函数的原型对象
也就是说 new 出来的实例和 class 构造函数有如下关系:实例.__proto__
== constructor.prototype
,形成了原型
而class 中除了constructor之外定义的方法,都会被添加到constructor.prototype中,所以 class 的结构让我们在创建原型方法的时候变得更加方便
而本质上还是实例对象与构造函数之间的相爱相杀!
18.js 中函数的 this 的绑定与流转
在 JavaScript 中,this
关键字的值(也称为执行上下文)是在函数调用时动态确定的,其流转方式可以通过以下规则来解释:
绑定:
- 默认绑定:如果函数是以独立函数的形式调用(即不是作为对象的方法、构造函数或使用
call()
、apply()
或bind()
进行绑定),则this
默认绑定到全局对象(在浏览器中为window
对象或在严格模式下为undefined
)。
注意:不管这个 function 在任何的位置(即使是类中),只要直接调用(就是直接 函数()
),this 都是window(严格模式为 undefined),并不会像箭头函数一样继承外层的 this,而是取决于谁调用的这个 function,如果直接使用了这个函数,那么都相当于是全局 window调用的(严格模式为 undefined)
function myFunction()
console.log(this);
}
myFunction(); // 在浏览器中输出 window,在严格模式下输出 undefined
- 隐式绑定:如果函数作为对象的方法(属性)调用,则
this
绑定到调用该方法的对象。
即对象里面的方法
//ES6 的简写写法:
const obj = {
name: "John",
sayHello() {
console.log(`Hello, ${this.name}!`);
}
};
//正常写法:
const obj = {
name: "John",
sayHello: function() {
console.log(`Hello, ${this.name}!`);
}
};
obj.sayHello(); // 输出 "Hello, John!"
- 显式绑定:通过使用
call()
、apply()
或bind()
方法,可以显式地指定函数的执行上下文。
call()
和 apply()
调用:使用 call()
或 apply()
方法可以显式地指定函数的执行上下文。call()
方法传递参数列表,而 apply()
方法接受一个参数数组。
bind()
方法可以显式地指定函数执行时的上下文,将函数的 this 绑定为一个对象。
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: "John" };
greet.call(person, "Hello"); // 输出 "Hello, John!"
greet.apply(person, ["Hi"]); // 输出 "Hi, John!"
const boundGreet = greet.bind(person); //将函数的 this 绑定到 person 对象上并返回一个新的对象
boundGreet("Hey"); // 输出 "Hey, John!"
- new 绑定:当函数作为构造函数使用
new
关键字调用时,会创建一个新的对象,并将该对象绑定到this
上。
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // 输出 "John"
- 箭头函数:箭头函数的
this
值继承自其定义时的父级作用域,而不是动态绑定。箭头函数没有自己的执行上下文,而是继承最近的非箭头函数的执行上下文。
继承最近的非箭头函数的执行上下文的情况:如果是函数那么就是 window,如果是类那么就是类,如果是对象那么就是对象,一般是这样的。
const obj = {
name: "John",
sayHello: () => {
console.log(`Hello, ${this.name}!`);
}
};
obj.sayHello(); // 输出 "Hello, undefined!"
在箭头函数中,this
的绑定是在函数定义时确定的,它会继承外部作用域的 this
值,而不是基于函数的调用方式。——> 只有这个箭头函数的特殊的(this 在定义时就确定了)
class类的函数的 this 绑定:
在类(class)中,函数的执行上下文(this
值)的绑定与普通函数有所不同。在类中定义的方法会自动绑定到类的实例上,而不是绑定到类本身。以下是类中 this
值的一些常见情况:
- 类的构造函数:类的构造函数通过
new
关键字调用,并且在实例化时会将this
绑定到新创建的实例上。
class Person {
constructor(name) {
this.name = name;
}
}
const john = new Person("John");
console.log(john.name); // 输出 "John"
- 类的方法:类中定义的方法会自动绑定到类的实例上,确保方法内部的
this
指向调用该方法的实例。
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
const john = new Person("John");
john.sayHello(); // 输出 "Hello, John!"
- 类的静态方法:静态方法属于类本身,而不是类的实例。在静态方法中,
this
指向类本身,而不是实例。
class MathUtils {
static square(x) {
return x * x;
}
}
console.log(MathUtils.square(5)); // 输出 25
在上述示例中,MathUtils.square()
是一个静态方法,可以直接通过类调用,而不需要实例化类。在静态方法中,this
指向类本身。
需要注意的是,在箭头函数中定义的方法不会自动绑定 this
,而是继承外部作用域的 this
。如果需要在类中使用箭头函数定义方法,要确保外部作用域的 this
与类的实例保持一致,这样箭头函数就可以访问类里面的属性了。——>这也是用箭头函数的方法解决 this 绑定问题的原理。
class Person {
constructor(name) {
this.name = name;
}
sayHello = () => {
console.log(`Hello, ${this.name}!`);
}
}
const john = new Person("John");
john.sayHello(); // 输出 "Hello, John!"
在上述示例中,通过箭头函数定义的 sayHello
方法会继承外部作用域的 this
,并确保与类的实例保持一致。
流转:方法由一个对象转到另一个对象,this会发生变化
例子:
const gery = new Person('Grey')
const mockDomButton = {} // 模拟一个DOM上的按钮对象
mockDomButton.onClick = gery.talk; // 绑定点击事件,把gery 实例上的方法绑定给 dom 对象作为一个属性,这样调用这个函数的对象就是 dom 对象了,
mockDomButton.onClick() // 输出的结果是 undefined says hello
在给 mockDomButton.onClick
赋值时,talk
方法的执行上下文(this
值)将丢失原本与 gery
实例的绑定关系。
当一个函数作为对象的方法调用时,其中的 this
值会绑定到调用该方法的对象。在这种情况下,onClick
属性被赋值为 gery.talk
方法。因此,当 onClick
方法被触发时,是 mockDomButton
对象调用了 talk 方法,所以talk方法的执行上下文中的 this
值将绑定到 mockDomButton
对象。
这意味着,在 talk
方法中,如果它依赖于 this
来引用 mockDomButton
对象的属性或方法,它将能够正常工作,如果它依赖于 this
来引用 gery
对象的属性或方法那么将是失败的 undefined。
19.解决mockDomButton.onClick = gery.talk;造成的 this 流转
mockDomButton.onClick = gery.talk;
在这种情况下,当 mockDomButton.onClick
被赋值为 gery.talk
方法时,talk
方法的执行上下文(this
值)将丢失原本与 gery
实例的绑定关系,并且在调用 onClick
方法时,talk
方法内部的 this
将绑定到mockDomButton
对象上。
这是因为在 JavaScript 中,当一个函数被提取出来并作为普通函数调用时,它的执行上下文(this
值)会发生改变,无法保留原始绑定关系。
如果你希望在 onClick
方法中保留与 gery
实例的this绑定关系,可以使用 bind()
方法或箭头函数来显式地绑定方法的执行上下文:
// 使用 bind 绑定
mockDomButton.onClick = gery.talk.bind(gery);
// 或者使用箭头函数
mockDomButton.onClick = () => gery.talk();
通过使用 bind(gery)
或箭头函数,可以确保在调用 onClick
方法( talk
方法)时,会在 gery
实例的上下文中执行,使得 this
在 talk
方法中指向 gery
实例。这样可以确保在 talk
方法内部,能够正常访问 gery
实例的属性和方法。
mockDomButton.onClick = () => gery.talk();这种方法非常巧妙,只是在talk 函数的外面包了一层,没有破坏原来的 this 指向关系,因为箭头函数的 this 在定义时就确定了,这里应该是mockDomButton对象,而这个箭头函数的返回值又是我们的 talk 函数,它并不受箭头函数的 this的影响,它的 this 还是原封不动的保持为 gery 对象
mockDomButton.onClick = () => gery.talk();等价于:
mockDomButton.onClick = () => {
return gery.talk();
};
20.箭头函数一行默认有 return
箭头函数在一行写法的情况下,默认会将单行表达式的结果作为返回值。这种隐式返回的行为只适用于单行箭头函数,当函数体超过一行时,需要显式使用 return
关键字来指定返回值。
下面是一个示例,展示了箭头函数一行写法的隐式返回:
const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出 5