FrankKai.github.io
FrankKai.github.io copied to clipboard
JavaScript实际使用中的细节记录
- for循环比forEach做break和return方便一些
- 过滤出存在至少一个mqtt协议的条目
- 实现一个sleep函数,并且每隔1秒打印出心形的一行
- 小数取整
- 十六进制色转RGB方便调整透明度
- await一个promise异常如何捕获?
- 计算属性不仅仅可用于拼接,还可以用于逻辑计算
- Object.is与==和===有什么不同?
- 如何实现一个浅比较?浅比较中的“浅”代表什么意思?
- proxy方式的属性监听(watch)
- encodeURI vs encodeURIComponent
- Function.prototye.bind()怎么用
- 可选链的执行过程
- e.target和e.currentTarget
- 巧用Promise.race加载2组CDN资源,使用先完成加载的CDN
- Proxy劫持对象,解决JSON.stringify()时属性不存在的问题
- 如何清除url的query,并且获取最新的url信息
for循环比forEach做break和return方便一些
- forEach适用于遍历整个数组,对于精确的条件控制并不适用(若想控制,需要将加入辅助变量)
- for(无论是for...of或是常规for)就没这烦恼,想break就break,想return就return
- forEach中的return, 相当于for与continue(label)的组合
举个例子: [1,2,3]我只想打印第一项的1。
forEach适用于遍历整个数组,对于精确的条件控制并不适用(若想控制,需要将加入辅助变量)
// 这种写法报错
[1,2,3].forEach((item)=>{
if(item===2) break ;
console.log(item);
})
=> SyntaxError: Illegal break statement
[1,2,3].forEach((item)=>{
if(item===2) return ;
console.log(item);
})
=> 1 3 (这个三其实你并不是想打印出来)
上面这种写法常用与函数中的return。
function test() {
[1,2,3].forEach((item)=>{
if(item===2) return;
console.log(item);
})
}
=> 1 3(这个三其实你并不是想打印出来)
for就没这烦恼,想break就break,想return就return
const arr = [1,2,3];
for(let i = 0;i<arr.length;i++){
if(arr[i]===2) break ;
console.log(arr[i]);
}
=> 1
const arr = [1,2,3];
for(let i = 0;i<arr.length;i++){
if(arr[i]===2) return ;
console.log(arr[i]);
}
=> SyntaxError: Illegal return statement
function test() {
const arr = [1,2,3];
for(let i = 0;i<arr.length;i++){
if(arr[i]===2) return;
console.log(arr[i]); // 1
}
}
let arr = [1,2,3];
for(let i of arr){
if(i===2) break ;
console.log(i); // 1
}
=> 1
forEach中的return, 相当于for与continue(label)的组合
[1,2,3].forEach((item)=>{
if(item===2) return ;
console.log(item);
})
=> 1, 3 等价于:
const arr = [1,2,3];
for(let i = 0;i<arr.length;i++){
if(arr[i]===2) continue;
console.log(arr[i]);
}
=> 1, 3
过滤出存在至少一个mqtt协议的条目
let protocols = [["http","https"], ['mqtts', 'tcp'],['ws', 'wss', 'https']]
let mqttProtocols = ['mqtt', 'mqtts', 'tcp', 'tls', 'ws', 'wss', 'wxs' , 'alis']
const validProtocols = protocols.filter((item)=>item.some((e)=>mqttProtocols.includes(e)))
JSON.stringify(validProtocols) // "[[\"mqtts\",\"tcp\"],[\"ws\",\"wss\",\"https\"]]"
实现一个sleep函数,并且每隔1秒打印出心形的一行
const heart = [" ***** *****"," ******* *******", "******************", " **************", " **********", " ****"]
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function heart(){
let arr = [" ***** *****"," ******* *******", "******************", " **************", " **********", " ****"];
for(const item of arr){
console.log(`%c ${item}`, 'color: red');
await sleep(1000);
}
}
await heart();
小数取整
位操作符(为了读懂别人的代码,不建议使用)
console.log(~~ 6.45) // 6
console.log(6.45 >> 0) // 6
console.log(6.45 << 0) // 6
console.log(6.45 | 0) // 6
// >>>不可对负数取整
console.log(6.45 >>> 0) // 6
parseInt(推荐使用,可读性好)
parseInt(6.45) // 6
parseInt(6.54) // 6
十六进制色转RGB(A)方便调整透明度
UI经常会给一个十六进制色,后面跟一个透明度。 而我们如果想调透明度是需要用rgba(x,y,z,a)的,其实这个a我们知道,但是x,y,z我们是不知道的。 下面这个方法可以很好实现转换。
function hexColorToRGB(hexColor, alpha){
let hexData = hexColor.split("");
if (hexData.length === 4) {
hexData = hexData.reduce((acc, cur) =>
cur === "#" ? [...acc, cur] : [...acc, cur, cur]
);
}
const rgbData = [];
let i = 1;
while(i < hexData.length){
const num = parseInt(`0x${hexData[i]}${hexData[i + 1]}`)
rgbData.push(num)
i = i + 2
}
if(alpha){
return `rgba(${rgbData[0]}, ${rgbData[1]}, ${rgbData[2]}, ${alpha})`
}
return `rgb(${rgbData[0]}, ${rgbData[1]}, ${rgbData[2]})`
}
hexColorToRGB("#0288D1")
hexColorToRGB("#ffffff", 0.16)
await一个promise异常如何捕获?
let promise = () => {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve("foo");
} else {
reject("bar");
}
});
};
async function test() {
let a = await promise().catch((e) => console.log("catch", e));
console.log(a);
}
拓展
1.如果没有做catch处理,console.log("hello world")会执行到吗? 会的。
await promise()
console.log("hello world") // 打印出hello world
2.reject后面的代码console.log("baz")会执行吗? 会的。
let promise = () => {
return new Promise((resolve, reject) => {
reject("bar");
console.log("baz")
});
};
await promise()
计算属性不仅仅可用于拼接,还可以用于逻辑计算
三元表达式
let key = '';
let obj = {
[key? 'foo': 'bar']: 'value'
}
obj {bar: 'value'}
或
let key = '';
let obj = {
[key || 'bar']: 'value'
}
obj {bar: 'value'}
Object.is与==和===有什么不同?
React中的浅比较是基于Object.is实现的。 那么Object与==、===有什么不同呢?
Object.is与==
==在比较两边的值之前,会先进行强制转换。而Oject.is不会
Object.is与===
===会将-0和+0当做相等的,将Number.NaN和NaN当做不相等的。而Oject会将其看做相当的
如何实现一个浅比较?
function shallowEqual(objA, objB) {
if (Object.is(objA, objB)) {
return true;
}
if (objA === null || typeof objA !== 'object' || objB === null || typeof objB !== 'object') {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) {
return false
}
for (let i = 0; i < keysA.length; i++) {
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true;
}
浅比较中的“浅”代表什么意思?
若对象属性值的引用相同,则不再深入比较,认为其是相等的。 提升虚拟dom Diff算法性能。
let shared = {c: 1}
let obj = {
a: 1,
b: shared
}
let obj1 = {
a: 1,
b: shared
}
console.log(shallowEqual(obj, obj1)) // true
let obj2 = {
a: 1,
b: {c: 1}
}
let obj3 = {
a: 1,
b: {c: 1}
}
console.log(shallowEqual(obj2, obj3)) // false
===等同于浅比较吗?
不是。 ===比较2个对象的引用。 浅比较比较对象的属性值的引用。 例如下面的例子:===为false,浅比较为true。
let shared = {c: 1}
let obj = {
a: 1,
b: shared
}
let obj1 = {
a: 1,
b: shared
}
obj === obj1 // false
shallowEqual(obj1, obj1) // true
浅比较,深比较,浅复制,深复制的核心是什么
核心是引用和全部。 浅:引用。 深:all。
浅比较:引用比较 浅复制:复制引用 深比较:对象的所有属性值全都要比较一遍 深复制:对象的所有属性值全都是新的
浅:性能好 深:状态独立
proxy方式的属性监听(watch)
// proxy watch
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log('set', value);
setBind(value);
return Reflect.set(target, property, value);
}
};
return new Proxy(obj, handler);
};
let obj = { a: 1 }
let value
let p = onWatch(obj, (v) => {
value = v
}, (target, property) => {
console.log(`Get '${property}' = ${target[property]}`);
})
// p.a = 2 // bind `value` to `2`
// p.a // -> Get 'a' = 2
let arr = [1,2,3]
let parr = []
let p1 = onWatch(arr, (v) => {
parr.push(v)
}, (target, property) => {
console.log(`Get '${property}' = ${target[property]}`);
})
// p1.push(1)
p1[0] = 9
p1
encodeURI vs encodeURIComponent
var set1 = ";,/?:@&=+$#"; // Reserved Characters
var set2 = "-_.!~*'()"; // Unreserved Marks
var set3 = "ABC abc 123"; // Alphanumeric Characters + Space
console.log(encodeURI(set1)); // ;,/?:@&=+$#
console.log(encodeURI(set2)); // -_.!~*'()
console.log(encodeURI(set3)); // ABC%20abc%20123 (the space gets encoded as %20)
console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24%23
console.log(encodeURIComponent(set2)); // -_.!~*'()
console.log(encodeURIComponent(set3)); // ABC%20abc%20123 (the space gets encoded as %20)
对于XMR方式的HTTP请求 GET,POST来说,encodeURI不会转码;,/?:@&=+$#字符,会把它们当做特殊字符。但是encodeURIComponent会转码,比如在输入邮箱的场景中,可能需要encodeURIComponent去对邮箱做转码,同时也需要服务端做响应的解码。
Function.prototye.bind()怎么用
bind函数运行后,会生成一个新的函数。新函数内部的this,会指向为bind的第一个参数。
const module = {
x: 42,
getX: function() {
return this.x;
}
};
const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42
这个例子中,bind的第一个参数为module,因此调用module.getX.bind(module)时,getX函数内部的this才能指向module,否则module.getX(), this指向的是window。
koa框架的中间件实现原理,也运用到了bind函数:用于返回一个全新的dispatch函数并且入参i要初始化为i+1。
function compose (middleware) {
return function (context, next) {
return dispatch(0)
function dispatch (i) {
let fn = middleware[i]
if (!fn) return Promise.resolve()
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
}
}
}
https://github.com/koajs/compose/blob/3ff7778e8d5c6dbc156a510b09df91aa2a7dded4/index.js#L42
还有一种情况:需要将 传入参数后的函数,作为prop传递 的情况。
例如
Foo({
callback: main.bind(null, ({ isRender: true })) // bind返回一个将{ isRender: true }数据传递了下去的新函数。
});
interface IFoo {
callback?: () => void;
}
function main(props?: { isRender?: boolean }) { ... }
bind返回一个传了部分参数的函数
A copy of the given function with the specified this value, and initial arguments (if provided). Calling the bound function generally results in the execution of its wrapped function.
let foo = (a, b) => {
console.log("a", a)
console.log("b", b)
}
let bindFoo = foo.bind(null, 1) // 注意,这返回的bindFoo,是一个只能接收1个参数,也就是b的函数
bindFoo(2)
// a 1 b 2
let foo = (a, b, c) => {
console.log("a", a)
console.log("b", b)
console.log("c", c)
}
let bindFoo = foo.bind(null, 1)
bindFoo(2, 3)
// a 1 b 2 c3
可以用来生成将后几个参数设为默认值的新函数
let foo = (a, b, c) => {
console.log("a", a);
console.log("b", b);
console.log("c", c);
}
let bindFoo = (input) => foo.bind(null, input, 1, 2);
bindFoo(42)(); // 假设 42 是你想绑定到 'a' 的值
可选链的执行过程
如果没有可选链语法,下面的代码逻辑是怎样的?
let nestedProp = obj.first?.second;
=>
let temp = obj.first;
let nestedProp = ((temp === null || temp === undefined) ? undefined : temp.second);
let target = data?.map((item) => item?.name);
=>
let temp = data;
let target = (temp === null || temp === undefined)? undefined: temp.map((item) => item?.name)
如果不能有左侧变量赋值,如何模拟
let data = [{name: 'foo'}, {name: 'bar'}];
data?.map((item) => item?.name)
=>
let data = [{name: 'foo'}, {name: 'bar'}];
function optionalChain(src, operation) {
let temp = src;
if(temp === null || temp === undefined){
return undefined;
}
return eval(`${JSON.stringify(temp)}${operation}`);
}
optionalChain(data, '.map((item) => item?.name)');
e.target和e.currentTarget
- e.target 事件发生的元素
- e.currentTarget 事件监听的元素
<!DOCTYPE html>
<html lang="en">
<body>
<div id="parent">
parent
<div id="child">child</div>
</div>
</body>
<script>
// target是事件发生对象
// currentTarget是事件监听对象
const parent = document.getElementById("parent");
// 为parent绑定事件
// 点击child target是child, currentTarget是parent(这是因为事件冒泡)
// 点击parent target和currentTarget都是parent
parent.onclick = (e) => {
console.log(e.target);
console.log(e.currentTarget);
};
</script>
</html>
巧用Promise.race加载2组CDN资源,使用先完成加载的CDN
let p 1= new Promise((resolve)=>{
setTimeout(()=>{console.log(1);resolve()}, 1000)
})
let p2 = new Promise((resolve)=>{
setTimeout(()=>{console.log(2);resolve()}, 2000)
})
let p3 = new Promise((resolve)=>{
setTimeout(()=>{console.log(3);resolve()}, 3000)
})
let p4 = new Promise((resolve)=>{
setTimeout(()=>{console.log(4);resolve()}, 1000)
})
await Promise.race([Promise.all([p1, p2]), Promise.all([p3, p4])])
console.log(5);
// 1,4,2,5,3
Proxy劫持对象,解决JSON.stringify()时属性不存在的问题
JSON.stringify()后的结果,比console.log的内容少。
console.log 的时候 targetNode 还没有被添加到 edge 上。 因为 JOSN.stringify()打印的是对象的快照而不是引用,所以 JOSN.stringify()时没有打印出结果。 但是因为 console.log 打印的是引用,所以也会打印出 TargetNode 。
可以用 Proxy 劫持下 edge ,当 targetNode 被添加时,打印对象。
把下面例子里的 targetObject 换成 edge 试下。
const targetObject = {};
const proxy = new Proxy(targetObject, {
defineProperty(target, property, descriptor) {
console.log(`试图向属性 ${property} 添加值`);
// 允许正常添加名为 "targetNode" 的属性
const result = Reflect.defineProperty(target, property, descriptor);
// 打印被劫持对象的值和修改后的对象
console.log('被劫持对象的值:', targetObject);
console.log('修改后的对象:', proxy);
return result;
},
});
// 通过代理添加属性
proxy.targetNode = "some value"; // 试图向属性 targetNode 添加值
// 输出: 被劫持对象的值: { targetNode: 'some value' }
// 输出: 修改后的对象: { targetNode: 'some value' }
如何清除url的query,并且获取最新的url信息
通过history.pushState清除url的query,通过location获取最新的url信息。
这要比通过window.location.href来清除url的query好的多,不会引起页面的刷新。另外说一句,更改window.location.pathname也会引起页面的刷新。
// 当前页面是http://yourdomain.com/page
var newUrl = '/newpage'; // 只能使用相对路径或与当前源相同的绝对路径
window.history.pushState({}, '', newUrl);
// 访问更新后的location对象
console.log(location.href); // http://yourdomain.com/newpage
console.log(location.pathname); // /newpage
console.log(location.hostname); // yourdomain.com
console.log(location.protocol); // http: