Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript实际使用中的细节记录 #223

Open
FrankKai opened this issue May 29, 2020 · 17 comments
Open

JavaScript实际使用中的细节记录 #223

FrankKai opened this issue May 29, 2020 · 17 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented May 29, 2020

  • 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信息
@FrankKai
Copy link
Owner Author

FrankKai commented May 29, 2020

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

@FrankKai
Copy link
Owner Author

FrankKai commented May 2, 2021

过滤出存在至少一个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\"]]"

@FrankKai
Copy link
Owner Author

FrankKai commented May 2, 2021

实现一个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();

@FrankKai
Copy link
Owner Author

小数取整

位操作符(为了读懂别人的代码,不建议使用)

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

@FrankKai
Copy link
Owner Author

FrankKai commented Jul 16, 2021

十六进制色转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)

@FrankKai
Copy link
Owner Author

FrankKai commented Jul 20, 2021

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()

@FrankKai
Copy link
Owner Author

FrankKai commented Jul 22, 2021

计算属性不仅仅可用于拼接,还可以用于逻辑计算

三元表达式

let key = '';
let obj = {
    [key? 'foo': 'bar']: 'value'
}

obj {bar: 'value'}

let key = '';
let obj = {
    [key || 'bar']: 'value'
}

obj {bar: 'value'}

@FrankKai
Copy link
Owner Author

Object.is与==和===有什么不同?

React中的浅比较是基于Object.is实现的。
那么Object与==、===有什么不同呢?

Object.is与==

==在比较两边的值之前,会先进行强制转换。而Oject.is不会

Object.is与===

===会将-0和+0当做相等的,将Number.NaN和NaN当做不相等的。而Oject会将其看做相当的

@FrankKai
Copy link
Owner Author

FrankKai commented Jul 23, 2021

如何实现一个浅比较?

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。

浅比较:引用比较 浅复制:复制引用
深比较:对象的所有属性值全都要比较一遍 深复制:对象的所有属性值全都是新的

浅:性能好
深:状态独立

@FrankKai
Copy link
Owner Author

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

@FrankKai
Copy link
Owner Author

FrankKai commented Jan 6, 2022

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去对邮箱做转码,同时也需要服务端做响应的解码。

@FrankKai
Copy link
Owner Author

FrankKai commented Feb 7, 2022

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' 的值

@FrankKai
Copy link
Owner Author

FrankKai commented Apr 14, 2022

可选链的执行过程

如果没有可选链语法,下面的代码逻辑是怎样的?

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)');

@FrankKai
Copy link
Owner Author

FrankKai commented May 9, 2022

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>

@FrankKai
Copy link
Owner Author

FrankKai commented May 24, 2022

巧用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

@FrankKai
Copy link
Owner Author

FrankKai commented Dec 8, 2023

Proxy劫持对象,解决JSON.stringify()时属性不存在的问题

JSON.stringify()后的结果,比console.log的内容少。
image

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' }

@FrankKai
Copy link
Owner Author

FrankKai commented May 31, 2024

如何清除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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant