JS知识点总结
文章目录
一、怎么可以设置对象的属性不可修改?
1、使用Object.defineProperty
[[Configurable]]
:表示属性是否可以通过delete
删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true
。[[Enumerable]]
:表示属性是否可以通过for-in
循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是true
。[[Writable]]
:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的 这个特性都是true
。[[Value]]
:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性 的默认值为undefined
。
let person = {
tjj: 1
};
Object.defineProperty(person, 'tjj', {
configurable: false,
writable: false
});
person.tjj = 12;
console.log(person.tjj); // 1
2、使用代理(Proxy)
const target = {
onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, {
set(target, property, value) {
// if (typeof value !== 'number') {
// return false;
// } else {
// return Reflect.set(...arguments);
// }
return false;
}
});
proxy.onlyNumbersGoHere = 2;
console.log(proxy.onlyNumbersGoHere); // 0
3、若使用TS
可以配置interface
的属性为readonly
。
二、深拷贝中的问题
1、JSON.parse(JSON.stringify(obj))有什么缺点?
废话不多说,直接上代码看:
const copyObj = {
arr: [1, 2, 3, { tjj: 'tjj' }],
fn: () => { // ×
console.log('123');
},
testBoolean: false,
testUndefined: undefined, // ×
testNull: null,
testObj: {
tjj: 1,
arr: [1, 2, { arr: [1, { tjj: 'tjj' }] }]
},
testSymbol: Symbol('tjj') // ×
};
const jsonCopyObj = JSON.parse(JSON.stringify(copyObj));
console.log(jsonCopyObj);
打印结果:
{ arr: [ 1, 2, 3, { tjj: 'tjj' } ],
testBoolean: false,
testNull: null,
testObj: { tjj: 1, arr: [ 1, 2, [Object] ] } }
可以看出来,这种方法可以实现递归去深拷贝源对象,但是不能拷贝函数,undefined,还有Symbol,同时由于会递归的去拷贝,还有可能会引起循环引用,导致爆栈,如:
const seven = {
name: 'seven'
}
const juejin = {
name: 'juejin',
qwe: seven
}
seven.relative = juejin
const newObj = JSON.parse(JSON.stringify(seven));
console.log(newObj)
就会报循环引用错:TypeError: Converting circular structure to JSON
。
2、手写深拷贝(考虑循环引用)
为了避免循环引用,我们用一个全局的WeakMap
保存copy
过的对象。当要copy
的是个对象时,判断是否copy
过,若copy
过直接返回result
,否则添加key-value
。
function deepCopy3(obj) {
const weakMap = new WeakMap();
return helper(obj, weakMap);
function helper(obj, weakMap) {
let result = null;
// 1.判断是对象类型还是普通数据类型,普通数据类型直接赋值
if (typeof obj === 'object') {
// 2.若是对象类型,进行细分:[],{},RegExp,null
// 2.1若是数组
if (Array.isArray(obj)) {
result = [];
for (let i = 0; i < obj.length; i++) {
// 递归判断一下若有对象,在进行深拷贝
result.push(helper(obj[i]), weakMap);
}
} else if (obj.constructor === RegExp) {
result = obj;
} else if (obj === null) {
result = obj;
} else { //普通对象
result = {};
// 若weakMap有obj,说明拷贝过,不需要再次拷贝,返回保存的值
if (weakMap.has(obj)) {
result = weakMap.get(obj);
return result;
} else { // 保存obj和其拷贝对象result
weakMap.set(obj, result);
}
// 保留原来对象类型,即原型上的信息
if (obj.__proto__) {
result.__proto__ = obj.__proto__;
}
const keys = Object.keys(obj);
for (let key of keys) {
result[key] = helper(obj[key], weakMap);
}
}
// RegExp,null可以直接赋值,[],{}需要递归
} else { // 普通数据类型或者函数
result = obj;
}
return result;
}
}
const seven = {
name: 'seven',
age: 22
}
const juejin = {
name: 'juejin',
qwe: seven
}
seven.relative = juejin
const copyObj2 = deepCopy3(seven);
console.log(copyObj2);
{ name: 'seven',
age: 22,
relative: { name: 'juejin', qwe: [Circular] } }
三、判断一个对象是否是空对象
1.JSON.stringify
function judgeObj2(emptyObj) {
return JSON.stringify(emptyObj) === '{}';
}
2.使用for…in…
function judgeObj(emptyObj) {
for (let key in emptyObj) {
// 当key为undefined时不会进入循环
return false
}
return true;
}
3.Object.keys
function judgeObj3(emptyObj) {
return Object.keys(emptyObj).length === 0;
}
4.Object.getOwnPropertyNames
function judgeObj4(emptyObj) {
return Object.getOwnPropertyNames(emptyObj).length === 0;
}
四、如何监听用户在一个页面上停留了多长时间?
1.多页面应用
可以监听onpageshow
,在页面展示的时候记录时间戳,再监听onpagehide
,在页面隐藏的时候计算时间长度。
let stopTime
window.onpageshow = ()=>{
stopTime = new Date().getTime()
}
window.onpagehide = ()=>{
stopTime = new Date().getTime() - stopTime
let record = localStorage.getItem('data')
let data = record && JSON.parse(record) || []
localStorage.setItem('data',JSON.stringify([...data,{user:new Date().getTime(),path:window.location.href,stopTime}]))
}
2.单页面应用
在单页面应用中,是通过history.pushState
来更新路由,渲染组件的。因此,我们需要监听pushState
事件。但pushState
并没有原生的监听事件,我们需要自定义一个事件,当调用pushState
时dispatch
这个事件。对pushState
和repalceState
进行改写。
const button = document.querySelector('.button');
button.addEventListener('click', function() {
history.pushState({
forward: 1
}, 'title', `${location.href}/path`);
})
// 对原函数做一个拓展
let rewriteHis = function(type) {
let origin = window.history[type] // 先将原函数存放起来
return function() { // 当window.history[type]函数被执行时,这个函数就会被执行
let rs = origin.apply(this, arguments) // 执行原函数
let e = new Event(type.toLocaleLowerCase()) // 定义一个自定义事件
e.arguments = arguments // 把默认参数,绑定到自定义事件上,new Event返回的结果,自身上是没有arguments的
window.dispatchEvent(e) // 触发自定义事件,把载荷传给自定义事件
return rs
}
}
window.history.pushState = rewriteHis('pushState') // 覆盖原来的pushState方法
window.history.replaceState = rewriteHis('replaceState') // 覆盖原来的replaceState方法
// 监听自定义事件, pushstate事件是在rewriteHis时注册的,不是原生事件
// 当点击router-link 或者 window.history.pushState 或者 this.$router.push 时都会被该事件监听到
window.addEventListener('pushstate', () => {
console.log('pushstate');
})
// 监听自定义事件, replacestate事件是在rewriteHis时注册的,不是原生事件
// 当点击window.history.replaceState 或者 this.$router.replace 时都会被该事件监听到
window.addEventListener('replacestate', () => {})
3.利用webSocket
利用webSocket
监听connet
和disconnect
时间来判断用户停留时间。
参考博文:
[1]: https://juejin.cn/post/6905913200060366862#heading-3