JavaScript中的继承方法
参考视频:面试官: JavaScript继承方式有哪些【原型链继承、构造函数继承、组合继承、寄生组合继承】_哔哩哔哩_bilibili
一、原型链继承
每个构造函数都有一个原型对象(prototype),每个实例对象包含一个指向原型对象的指针(__porto__)。这样,每当代码读取实例的某个属性时,都会首先在实例上搜索这个属性,如果没有找到,则会搜素原型对象。
//定义父类
function Parent(name) {
this.name = "parent";
}
//在父类的原型上定义方法
Parent.prototype.getName = function(){
return this.name;
}
//定义子类
function Child(){
this.name="child";
}
//子类继承父类,这里是关键字,实现原型链继承
Child.prototype = new Parent();
//子类实例化
var child1 = new Child();
//测试
console.log(child1.getName());
//定义子类的子类
function Son(){
this.name = "Son";
}
//再实现一层继承
Son.prototype = new Child();
//实例化
var son1 = new Son();
//测试
console.log(son1.getName());
缺点:原型链继承的主要问题是包含引用类型值的原型属性会被所有实例共享。简单理解就是,一个实例改变了某种属性,其他被同样创建出来的实例的该属性也会被改变。
//定义父类
function Parent(name) {
this.arr = [1,2,3];
}
//定义子类
function Child() {}
//子类继承父类
Child.prototype = new Parent();
//实例化子类
var child1 = new Child();
var child2 = new Child();
//插入元素 4
child1.arr.push(4);
console.log(child1.arr);
console.log(child2.arr);
二、构造函数继承
构造函数继承,通过只用call或apply方法,我们可以在子类中执行父类型构造函数,从而实现继承。
//父类
function Parent() {
this.sayHello = function() {
console.log("Hello");
};
}
Parent.prototype.a = "父类prototype上的属性";
//子类
function Child() {
Parent.call(this);
}
//创建三个实例
var child1 = new Child();
var child2 = new Child();
var parent = new Parent();
console.log(child1.sayHello === child2.sayHello);
console.log(parent.a);
console.log(child1.a);
优点:这种继承方式好处就在于其原型属性不会被共享(也就不会出现原型链继承时出现的情况)。
缺点:不能继承父类上prototype上的属性。
三、组合继承(原型链继承+构造函数继承)
//父类
function Parent() {
this.sayHello = function() {
console.log("Hello");
};
}
Parent.prototype.a = "父类prototype上的属性";
//子类
function Child() {
Parent.call(this);
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.a);
优点:1.原型属性不会被共享。2.可以继承父类的原型链上的属性和方法
缺点:调用了 2 次Parent()。他在child的prototype上添加了父类的属性和方法。
四、寄生组合继承
Object.create()方法介绍
创建一个空对象,并且可以指定他的空对象原型(prototype)是谁。
var proto = {
sayHello: function () {
console.log("Hello");
},
};
var obj = Object.create(proto);
obj.sayHello();
寄生组合继承实现方法
//父类
function Parent() {
this.sayHello = function() {
console.log("Hello");
};
}
Parent.prototype.a = "我是父类prototype上的属性";
//子类
function Child() {
Parent.call(this);
}
Child.prototype.childfunction = ()=>{
console.log("我是child方法");
};
//创建一个没有实例方法的父类实例作为子类的原型
Child.prototype = Object.create(Parent.prototype);
//修复构造函数的指向
Child.prototype.constructor = Child;
var child = new Child();
child.childfunction();
优点:
1.原型属性不会被共享
2.可以继承父类的属性和方法
3.只调用了 1 次 Parent()。因此,它不会在 Child 的 prototype 上添加 Parent 的属性和方法
缺点:
Child.prototype的原始属性和方法会丢失。
财务管理微信小程序代码的定位部分代码
代码位于xbill-ui/src/pages/bill/edit.vue中
onSwitchChange() {
// console.log("获取位置", this.locationStatus);
if (this.locationStatus) {
this.getLocation();
} else {
this.$tip.toast("长按开启自动获取地址哦!");
}
},
// 位置信息获取开关切换(长按开关)
onSwitchLongtap() {
// console.log("长按");
this.locationStatus = !this.locationStatus;
uni.setStorageSync(LOCATION_STATUS, this.locationStatus);
if (this.locationStatus) {
this.getLocation();
this.$tip.toast("自动获取地址信息已开启");
} else {
// 关闭获取地理位置
// this.model.address = "";
this.$tip.toast("自动获取地址信息已关闭");
}
},
// 获取定位地址
getLocation() {
if (this.locationStatus)
Location.getLocation().then((res) => {
// console.log("位置信息", res);
this.model.address = res.address;
});
},
第一部分
onSwitchChange() {
// console.log("获取位置", this.locationStatus);
if (this.locationStatus) {
this.getLocation();
} else {
this.$tip.toast("长按开启自动获取地址哦!");
}
},
onSwitchChange()函数内通过条件判断locationStatus的值,意为是都获取到位置,进而选择执行对应的操作。若locationStatus值为真,则调用getLocation()函数得到此时的地址;若locationStatus值为假,则会通过this.$tip.toast方法显示弹出框来提示用户该如何开启定位相关信息。
这里的locationStatus值是前部分函数通过UniApp 框架的 API 来从本地存储中同步获取数据的方法。
locationStatus: uni.getStorageSync(LOCATION_STATUS) || false
uni.getStorageSync(KEY) 从本地缓存中同步获取指定 key 对应的内容。
通过 || false 运算可以将得到的数据进行转换,将locationStatus的类型变成布尔型,非常灵活。
第二部分
onSwitchLongtap() {
// console.log("长按");
this.locationStatus = !this.locationStatus;
uni.setStorageSync(LOCATION_STATUS, this.locationStatus);
if (this.locationStatus) {
this.getLocation();
this.$tip.toast("自动获取地址信息已开启");
} else {
// 关闭获取地理位置
// this.model.address = "";
this.$tip.toast("自动获取地址信息已关闭");
}
},
首先,这个函数使用触发事件调用的,我们可以看一下触发的条件
@longtap="onSwitchLongtap"
这里的@longtap,搜索后我们可以了解到是通过长时间按住某个图标或区块才可以触发。
然后,再来看函数主体部分,首先对locationStatus的状态值进行相逆的改变,并且将LOCATION_STATUS和this.locationStatus的值通过uni.setStorageSync()函数同步的存到本地存储上去了。
其次,根据每次触发事件时,locationStatus改变后的值条件判断,执行不同的内容语句。
如果locationStatus
为true
(即地理位置自动获取功能已开启):调用this.getLocation();
方法,这个方法可能用于获取用户的当前地理位置信息。使用this.$tip.toast("自动获取地址信息已开启");
显示一个提示信息,告知用户自动获取地址信息的功能已经开启。
如果locationStatus
为false
(即地理位置自动获取功能已关闭):使用this.$tip.toast("自动获取地址信息已关闭");
显示一个提示信息,告知用户自动获取地址信息的功能已经关闭。
第三部分
getLocation() {
if (this.locationStatus)
Location.getLocation().then((res) => {
// console.log("位置信息", res);
this.model.address = res.address;
});
},
当this.locationStatus的值为true时,会执行函数操作,ocation.getLocation().then((res) => { ... });
这里调用了 Location
对象的 getLocation
方法来异步获取地理位置信息。getLocation
方法返回一个 Promise 对象,该对象在成功获取到位置信息时会被解析(resolve),并传递一个包含位置信息的对象(这里用 res
表示)作为参数给 .then
方法中的回调函数。在 .then
方法的回调函数中,res
参数包含了从 getLocation
方法中返回的位置信息。
Location
import Location from "@/common/utils/location";
我们发现Location其实是从文件中引入的,我们根据路径查找对应调用的函数看一下
getLocation() {
let { locationEnabled } = wx.getSystemInfoSync();
if (locationEnabled) {
return new Promise((resolve, reject) => {
let curTime = new Date().getTime();
let time = uni.getStorageSync(GET_LOCATION_TIME) || curTime - 60005 // 加一分多钟
let timeDiff = curTime - time
let leavel = timeDiff % (3600 * 1000); // 计算剩余小时后剩余的毫秒数
let sec = Math.floor(leavel / 1000); // 计算剩余的分钟数
// console.log(curTime, time, leavel, sec);
if (sec < 60) {
this.formatLocation().then((res) => {
resolve(res);
}).catch(err => {
tip.error("获取地址失败")
console.log("reverseGeocoder获取地址信息失败", err);
reject(err)
})
} else {
this.getUserLocation().then(res => {
uni.setStorageSync(GET_LOCATION_TIME, curTime)
this.formatLocation().then((res) => {
resolve(res);
}).catch(err => {
tip.error("获取地址失败")
console.log("reverseGeocoder获取地址信息失败", err);
reject(err)
})
}).catch(err => {
tip.error("获取位置失败")
console.log("getLocation获取地理位置失败", err);
reject(err)
})
}
});
} else {
tip.toast('请检查手机定位是否开启')
reject("未开启手机定位")
}
},
- 检查定位是否启用:
- 使用
wx.getSystemInfoSync()
方法(这看起来像是微信小程序或类似框架的API)来获取系统信息,并从中解构出locationEnabled
属性,以判断手机的定位功能是否启用。 - 如果
locationEnabled
为false
,则显示一个提示信息("请检查手机定位是否开启"),并通过reject
返回一个错误("未开启手机定位"),从而中断 Promise 的执行。
- 使用
- 计算上次获取位置的时间差:
- 使用
uni.getStorageSync(GET_LOCATION_TIME)
从本地存储中获取上一次成功获取位置信息的时间戳(如果不存在,则默认为一个比当前时间早一分多钟的时间戳)。 - 计算当前时间与上次获取时间的时间差(
timeDiff
),然后计算这个时间差对应的秒数(sec
),但这里的计算实际上只考虑了秒数(尽管变量名为sec
,实际上它表示的是从上次获取位置到现在的总秒数,而不是仅剩余的秒数)。
- 使用
- 根据时间差决定操作:
- 如果时间差小于60秒(即如果距离上次获取位置信息不到一分钟),则直接调用
formatLocation
方法来尝试格式化当前的位置信息(尽管这里没有直接从任何地方获取当前位置,这可能是一个假设或遗漏)。 - 如果时间差大于或等于60秒,则调用
getUserLocation
方法来获取最新的位置信息,并在成功获取后更新本地存储中的时间戳为当前时间。然后,使用获取到的位置信息调用formatLocation
方法进行格式化。
- 如果时间差小于60秒(即如果距离上次获取位置信息不到一分钟),则直接调用
- 错误处理:
- 在调用
formatLocation
和getUserLocation
方法时,都使用了.then()
和.catch()
来处理成功和失败的情况。如果formatLocation
或getUserLocation
失败,将显示一个错误提示,并在控制台打印错误信息,最后通过reject
返回错误,以便调用者可以处理这些错误情况。
- 在调用
- 注意:
- 代码中
GET_LOCATION_TIME
应该是一个常量或变量,用于在本地存储中标识存储上次获取位置信息时间戳的键。 tip.error
和tip.toast
可能是用于显示提示信息的自定义方法或库函数。- 这里的
formatLocation
方法没有在代码片段中定义,但从上下文来看,它应该是一个将位置信息(如经纬度)转换为人类可读地址信息的方法。 getUserLocation
方法同样没有在代码片段中定义,但从上下文可以推断,它应该是一个用于获取用户当前位置信息的方法。
- 代码中