FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

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

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago
FrankKai commented 4 years ago

for循环比forEach做break和return方便一些

举个例子: [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 commented 3 years ago

过滤出存在至少一个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 commented 3 years ago

实现一个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 commented 3 years ago

小数取整

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

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 commented 2 years ago

十六进制色转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 commented 2 years ago

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 commented 2 years ago

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

三元表达式

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

obj {bar: 'value'}

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

obj {bar: 'value'}

FrankKai commented 2 years ago

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

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

Object.is与==

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

Object.is与===

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

FrankKai commented 2 years ago

如何实现一个浅比较?

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

可选链的执行过程

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

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 commented 2 years ago

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 commented 2 years ago

巧用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 commented 7 months ago

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 commented 1 month ago

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