# 1. 一、开始

近期整理了一些“收藏”,主要是日常开发中遇到过的问题和小知识点。

# 2. 二、前端

# 2.1. .gitignore忽略文件夹中除了指定的文件外的其他所有文件

只有2级目录:

//.ignore忽略文件夹中除了指定的文件外的其他所有文件
**/node_modules/*//忽略文件夹node_modules
!**/node_modules/vue-html5-editor/ //不包含vue-html5-editor文件夹

存在多级目录时:

//错误写法
**/node_modules/*//忽略文件夹node_modules
!**/node_modules/vue-html5-editor/dist/  //这样写是不生效的

//正确写法
**/node_modules/*
!**/node_modules/vue-html5-editor/
**/node_modules/vue-html5-editor/*
!**/node_modules/vue-html5-editor/dist

参考:.gitignore忽略文件夹中除了指定的文件外的其他所有文件 (opens new window)

# 2.2. 正确的清除cookie

清除cookie方式是设置为过期,切记设置domain和path,只有这两个参数跟你要删除的参数完全一样才能把它删除掉。

参考:Javascript清除cookie的方法 (opens new window)

# 2.3. JS设置CSS样式的几种方式

  1. 直接设置style的属性,属性名用驼峰写法

比如:

element.style.height = '100px';
  1. setAttribute
element.setAttribute('height', 100);
element.setAttribute('height', '100px');
  1. setAttribute设置style
element.setAttribute('style', 'height: 100px !important');
  1. 使用setProperty。如果要设置!important,推荐用这种方法设置第三个参数,属性名不用驼峰写法
element.style.setProperty('height', '300px', 'important');
  1. 改变class
element.className = 'blue';
element.className += 'blue fb';
  1. 设置cssText
element.style.cssText = 'height: 100px !important';
element.style.cssText += 'height: 100px !important';
  1. 创建并引入新的CSS样式文件
function addNewStyle(newStyle) {
  let styleElement = document.getElementById('styles_js');

  if (!styleElement) {
    styleElement = document.createElement('style');
    styleElement.type = 'text/css';
    styleElement.id = 'styles_js';
    document.getElementsByTagName('head')[0].appendChild(styleElement);
  }
  styleElement.appendChild(document.createTextNode(newStyle));
}

addNewStyle('.box {height: 100px !important;}');
  1. 使用addRule、insertRule
// 在原有样式操作
document.styleSheets[0].addRule('.box', 'height: 100px');
document.styleSheets[0].insertRule('.box {height: 100px}', 0);

// 或者插入新样式时操作
const styleEl = document.createElement('style');
const styleSheet = styleEl.sheet;
styleSheet.addRule('.box', 'height: 100px');
styleSheet.insertRule('.box {height: 100px}', 0);
document.head.appendChild(styleEl);

参考:JS设置CSS样式的几种方式 (opens new window)

# 2.4. CSS3 animation动画中forwards和both的区别

如果动画无延迟,forwards 和 both 表现出来的效果是相同的;在有延迟的时候表现出来的效果才会不同,forwards 和 both 的结束效果相同(停留在最后一个关键帧中定义的状态),开始时 both 会应用 backwards(第一帧定义的状态) 的效果

参考:一次搞懂 CSS3 animation动画中forwards和both的区别 (opens new window)

# 2.5. Webpack3和4的区别

  1. 增加了一个mode配置,只有两种值development | production
  2. 移除了webpack.optimize.CommonsChunkPlugin。可使用optimization.splitChunks进行模块划分(提取公用代码)。
  3. 删除原 extract-text-webpack-plugin 配置,增加 mini-css-extract-plugin 配置。
  4. 移除UglifyJsPlugin,只需要使用optimization.minimize为true就行,production mode下面自动为true
  5. 移除loaders,必须使用rules。

参考:WEBPACK3和WEBPACK4区别 (opens new window)

# 2.6. Nodejs接收图片base64格式保存为文件

base64的形式为“data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0...”

当接收到上边的内容后,需要将data:image/png;base64,这段内容过滤掉,过滤成:“iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0...”;然后进行保存。

const fs=require('fs');
app.post('/upload', function(req, res){
    //接收前台POST过来的base64
    var imgData = req.body.imgData;
    //过滤data:URL
    var base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
    // 返回一个被 string 的值初始化的新的 Buffer 实例,原始二进制数据存储在 Buffer 类的实例中,        
    // 一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。
    var dataBuffer = Buffer.from(base64Data, 'base64');
    fs.writeFile("image.png", dataBuffer, function(err) {
        if(err){
          res.send(err);
        }else{
          res.send("保存成功!");
        }
    });
});

参考:Nodejs接收图片base64格式保存为文件 (opens new window)

# 2.7. 字符串中的换行符\r\n<br>不生效问题

在css中设置:

white-space: pre-line; // 合并空白符序列,但是保留换行符 让换行符生效

# 2.8. 进制转换

js中的两个内置方法 toString 和 parseInt。

toString 可以把一个数转换为指定进制的数,parseInt 是把数按照指定进制解析成十进制的数。

  1. 任意进制转换成十进制
console.log(parseInt('11001',2)) // 25
console.log(parseInt('007F',16)) // 127
  1. 十进制转成任意进制
(25).toString(2) // 11001

顺便贴下字符与Unicode(其实是UTF-16代码单元)互相转化:

"a".charCodeAt(0)
// 97

String.fromCharCode(97)
// "a"

# 2.9. 可选链操作符属性为数组

可选链操作符,数组的话,可以 list?.[1]

也可以 let nestedProp = obj?.['prop' + 'Name'];

# 2.10. TS报类型错误

报类型错误,可以直接(pvpapp as any).invoke

# 2.11. 异步函数调用

异步函数调用的时候不能用await,要用then,不然某些考试编辑器会超时。

# 2.12. 查看npm全局包下载路径

# 查看全局包下载路径
$ npm root -g

# 查看命令下载路径
$ npm bin -g

# 2.13. 插槽

插槽就是在组件引用的内部增加额外的内容,比如一个子组件名字是Comp,父组件这样使用:<Comp>hello</Comp>hello所处的位置就是slot插槽。

# 2.14. 实现一个任务调度器,将多个任务依次执行,并且上一个任务的结果作为下一个任务的参数

function taskRunner() {
  // TODO
}

function step1(a) {
  return `1-${a}`
}

async function step2(b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`2-${b}`)
    }, 2000)
  })
}


const task = taskRunner(step1, step2)

const res = task(0)

实现:

function taskRunner() {
  const [...steps] = [...arguments];

  return async function (...args) {
    let tempRes;

    for (let i = 0;i < steps.length;i++) {
      const step = steps[i];
      if (i == 0) {
        tempRes = await Promise.resolve(step(...args));
      } else {
        tempRes = await Promise.resolve(step(tempRes));
      }
    }

    return tempRes;
  };
}

const task = taskRunner(step1, step2)

const res = task(0)

res.then(res => {
  console.log('res', res)
})
// res 2-1-0

# 2.15. 将对象的key由下划线转为驼峰,只转化key

function toHump(str) {
  return str.replace(/\_(\w)/g, (a,b) => {
    return b.toUpperCase()
  })
}

function objToHump(obj, cache = new WeakMap()) {
  if (!(obj instanceof Object)) {
    return obj;
  }

  if (cache.get(obj)) {
    return cache.get(obj);
  }

  const res = {};
  cache.set(obj, res);

  Object.keys(obj).map((key) => {
    const newKey = toHump(key);

    if (obj[key] instanceof Object) {
      if (Array.isArray(obj[key])) {
        res[newKey] = obj[key].map(cObj => objToHump(cObj, cache));
      } else {
        res[newKey] = objToHump(obj[key], cache);
      }
    } else {
      res[newKey] = obj[key];
    }
  });

  return res;
}


const obj = {
  a_apple_p: 'aa',
  a_apple_q: {
    a_k_c: '1',
  },
  a_apple_u: {
    a_apple_h: {
      a_apple_i: '123',
    },
  },
  a_apple_t: [
    {
      a_k_c: '2',
      dd_dd_dd: '3',
    },
    {
      a_k_d: '5',
      dd_dd_de: '6',
    },
  ],
};

obj.t_t_t = obj;

console.log(objToHump(obj));

/**
 * {
      aAppleP: 'aa',
      aAppleQ: { aKC: '1' },
      aAppleU: { aAppleH: { aAppleI: '123' } },
      aAppleT: [ { aKC: '2', ddDdDd: '3' }, { aKD: '5', ddDdDe: '6' } ],
      tTT: [Circular *1]
    }
 */

# 2.16. 理解 TypeScript 中 any 和 unknown

# 2.16.1. (1)Any

any 类型类似于纯 JavaScript 的工作方式。我们有时可能需要描述一个我们根本不知道类型的变量。

let uncertain: any = 'Hello world'!;
uncertain = 5;
uncertain = { hello: () => 'Hello world!' };

在 TypeScript 中,任何东西可以赋值给 any 。它通常被称为 top type 。

any 和 unknown 的最大区别是, unknown 是 top type (任何类型都是它的 subtype) , 而 any 即是 top type, 又是 bottom type (它是任何类型的 subtype ) , 这导致 any 基本上就是放弃了任何类型检查.

any 破坏了 TypeScript 的类型检查,会引起错误:

const uncertain: any = 'Hello world!';
uncertain.hello();

下面是更接近实际的例子:

const dog: any = {
 name: 'Fluffy',
 sayHello: () => 'woof woof'
};

dog.hello();

# 2.16.2. (2)Unknown

TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给unknown。

let uncertain: unknown = 'Hello'!;
uncertain = 12;
uncertain = { hello: () => 'Hello!' };

我们只能将 unknown 类型的变量赋值给 any 和 unknown。

let uncertain: unknown = 'Hello'!;
let notSure: any = uncertain;

它确实在很多方面不同于 any 类型。如果不缩小类型,就无法对 unknown 类型执行任何操作。

function getDog() {
return '22'
}

const dog: unknown = getDog();
dog.hello(); //Object is of type 'unknown'
# 2.16.2.1. a. 使用类型断言缩小未知范围

上述机制具有很强的预防性,但对我们的限制过于有限。要对未知类型执行某些操作,首先需要使用类型断言来缩小范围。

const getDogName = () => {
let x: unknown;
return x;
};

const dogName = getDogName();
console.log((dogName as string).toLowerCase());

在上面的代码中,我们强制TypeScript编译器相信我们知道自己在做什么。

以上的一个重要缺点是它只是一个假设。它没有运行时效果,也不能防止我们在不小心的情况下造成错误。比如下面的代码, 他实际上是错误的, 但却可以通过 typescript 的检测.

const number: unknown = 15;
(number as string).toLowerCase();

TypeScript编译器接收到我们的数字是一个字符串的假设,因此它并不反对这样处理它。

# 2.16.2.2. b. 使用类型收缩

一种更类型安全的缩小未知类型的方法是使用 类型收缩 。TypeScript 编译器会分析我们的代码,并找出一个更窄的类型。

const dogName = getDogName();
if (typeof dogName === 'string') {
  console.log(dogName.toLowerCase());
}

在上面的代码中,我们在运行时检查了 dogName 变量的类型。因此,我们可以确保只在 dogName 是变量时调用 toLowerCase函数。

除了使用 typeof,我们还可以使用 instanceof 来缩小变量的类型。

type getAnimal = () => unknown;

const dog = getAnimal();

if (dog instanceof Dog) {
 console.log(dog.name.toLowerCase());
}

在上面的代码中,我们确保只有在变量是某个原型的实例时才调用dog.name.toLowerCase。TypeScript编译器理解这一点,并假设类型。

# 2.16.2.3. c. 联合类型中的 unknown 类型

在联合类型中,unknown 类型会吸收任何类型。这就意味着如果任一组成类型是 unknown,联合类型也会相当于 unknown:

type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown

这条规则的一个意外是 any 类型。如果至少一种组成类型是 any,联合类型会相当于 any:

type UnionType5 = unknown | any;  // any

为什么 unknown 可以吸收任何类型(any 类型除外)?让我们来想想 unknown | string 这个例子。这个类型可以表示任何 unkown 类型或者 string 类型的值。就像我们之前了解到的,所有类型的值都可以被定义为 unknown 类型,其中也包括了所有的 string 类型,因此,unknown | string 就是表示和 unknown 类型本身相同的值集。因此,编译器可以将联合类型简化为 unknown 类型。

# 2.16.2.4. d. 交叉类型中的 unknown 类型

在交叉类型中,任何类型都可以吸收 unknown 类型。这意味着将任何类型与 unknown 相交不会改变结果类型:

type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

让我们回顾一下 IntersectionType3:unknown & string 类型表示所有可以被同时赋值给 unknown 和 string 类型的值。由于每种类型都可以赋值给 unknown 类型,所以在交叉类型中包含 unknown 不会改变结果。我们将只剩下 string 类型。

# 2.16.2.5. e. 示例:从 localStorage 中读取JSON

这是一个使用 unknown 类型的真实例子。

假设我们要编写一个从 localStorage 读取值并将其反序列化为 JSON 的函数。如果该项不存在或者是无效 JSON,则该函数应返回错误结果,否则,它应该反序列化并返回值。

因为我们不知道在反序列化持久化的 JSON 字符串后我们会得到什么类型的值。我们将使用 unknown 作为反序列化值的类型。这意味着我们函数的调用者必须在对返回值执行操作之前进行某种形式的检查(或者使用类型断言)。

实现如下:

type Result =
  | { success: true, value: unknown }
  | { success: false, error: Error };
 
function tryDeserializeLocalStorageItem(key: string): Result {
  const item = localStorage.getItem(key);
 
  if (item === null) {
    // The item does not exist, thus return an error result
    return {
      success: false,
      error: new Error(`Item with key "${key}" does not exist`)
    };
  }
 
  let value: unknown;
 
  try {
    value = JSON.parse(item);
  } catch (error) {
    // The item is not valid JSON, thus return an error result
    return {
      success: false,
      error
    };
  }
 
  // Everything's fine, thus return a success result
  return {
    success: true,
    value
  };
}

tryDeserializeLocalStorageItem 的函数调用者在尝试使用 value 或 error 属性之前必须首先检查 success 属性:

const result = tryDeserializeLocalStorageItem("dark_mode");
 
if (result.success) {
  // We've narrowed the `success` property to `true`,
  // so we can access the `value` property
  const darkModeEnabled: unknown = result.value;
 
  if (typeof darkModeEnabled === "boolean") {
    // We've narrowed the `unknown` type to `boolean`,
    // so we can safely use `darkModeEnabled` as a boolean
    console.log("Dark mode enabled: " + darkModeEnabled);
  }
} else {
  // We've narrowed the `success` property to `false`,
  // so we can access the `error` property
  console.error(result.error);
}

请注意,tryDeserializeLocalStorageItem 函数不能简单地通过返回 null 来表示反序列化失败,原因如下:

  1. null 值是一个有效的 JSON 值。因此,我们无法区分是对值 null 进行了反序列化,还是由于缺少参数或语法错误而导致整个操作失败。
  2. 如果我们从函数返回 null,我们无法同时返回错误。因此,我们函数的调用者不知道操作失败的原因。

# 2.16.3. (3)小结

unknown 类型要安全得多,因为它迫使我们执行额外的类型检查来对变量执行操作。

参考:

  1. [译] 理解 TypeScript 中 any 和 unknown (opens new window)
  2. [译] TypeScript 3.0: unknown 类型 (opens new window)

# 2.17. 填写Promise的返回值类型

介绍完 unknown 类型后,看下下面这个题:

lib.d.ts:

type SomeProp = unknown;
export declare function getSomeProp(): Promise<SomeProp>;

main.ts:

import { getSomeProp } from './lib';

/**
 * 请实现下面这个类型,使得下面的 `main` 函数能通过 TypeScript 的类型检查。
 * 注意:不能修改 lib.d.ts 文件。
 */
type Result = /* REPLACE */ any;

async function main() {
  const res: Result = await getSomeProp();
}

这里我的理解是 unknown 类型的变量只能赋值给 any 或 unknown,那么这里不是 any 也就是 unknown 了。

# 2.18. Passive Event Listeners

addEventListener():大家都是认识的,为dom添加触发事件。

在早期addEventListener是这样的:

addEventListener(type, listener, useCapture)

useCapture:是否允许事件捕捉,但是很少会传true,然后就变成可选项了:

addEventListener(type, listener[, useCapture ])

到现在就变成了这个样子:

addEventListener(type, listener, {
    capture: false, //捕获
    passive: false, 
    once: false    //只触发一次
})

如果我们在 touchstart 事件调用 preventDefault 会怎样呢?这时页面会禁止,不会滚动或缩放。那么问题来了:浏览器无法预先知道一个监听器会不会调用 preventDefault(),它需要等监听器执行完后,再去执行默认行为,而监听器执行是要耗时的,这样就会导致页面卡顿。

当你触摸滑动页面时,页面应该跟随手指一起滚动。而此时你绑定了一个 touchstart 事件,你的事件大概执行 200 毫秒。这时浏览器就犯迷糊了:如果你在事件绑定函数中调用了 preventDefault,那么页面就不应该滚动,如果你没有调用 preventDefault,页面就需要滚动。但是你到底调用了还是没有调用,浏览器不知道。只能先执行你的函数,等 200 毫秒后,绑定事件执行完了,浏览器才知道,“哦,原来你没有阻止默认行为,好的,我马上滚”。此时,页面开始滚。

当浏览器等待执行事件的默认行为时,大部分情况是白等了。如果 Web 开发者能够提前告诉浏览器:“我不调用 preventDefault 函数来阻止事件事件行为”,那么浏览器就能快速生成事件,从而提升页面性能。

Chrome官方有个视频测试:https://www.youtube.com/watch?v=NPM6172J22g

而 passive 就是为此而生的。在 WICG 的 demo 中提到,即使滚动事件里面写一个死循环,浏览器也能够正常处理页面的滑动。

在最新的 DOM 规范中,事件绑定函数的第三个参数变成了对象:

target.addEventListener(type, listener[, options]);

我们可以通过传递 passive 为 true 来明确告诉浏览器,事件处理程序不会调用 preventDefault 来阻止默认滑动行为。

在 Chrome 浏览器中,如果发现耗时超过 100 毫秒的非 passive 的监听器,会在 DevTools 里面警告你加上 {passive: true}

参考:

  1. 移动Web滚动性能优化: Passive event listeners (opens new window)
  2. passive的作用和原理 (opens new window)
  3. 让页面滑动流畅得飞起的新特性:Passive Event Listeners (opens new window)

# 2.19. Chrome渲染原理

上面的参考文章中,讲到了Chrome的渲染过程中的几个概念:

  • 绘制(Paint):将绘制操作转换成为图像的过程(比如软件模式下经过光栅化生成位图,硬件模式下经过光栅化生成纹理)。在Chrome中,绘制分为两部分实现:绘制操作记录部分(main-thread side)和绘制实现部分(impl-side)。绘制记录部分将绘制操作记录到SKPicture中,绘制实现部分负责将SKPicture进行光栅化转成图像;
  • 图层(Paint Layer):在Chrome中,页面的绘制是分层绘制的,页面内容变化的时候,浏览器仅需要重新绘制内容变化的图层,没有变化的图层不需要重新绘制;
  • 合成(Composite):将绘制好的图层图像混合在一起生成一张最终的图像显示在屏幕上的过程;
  • 渲染(Render):可以简单认为渲染等价于绘制+合成;
  • UI线程(UI Thread):浏览器的主线程,负责接收到系统派发给浏览器窗口的事件、资源下载等;
  • 内核线程(Main/Render Thread):Blink内核及V8引擎运行的线程,如DOM树构建、元素布局、绘制(main-thread side)、JavaScript执行等逻辑在该线程中执行;
  • 合成线程(Compositor Thread):负责图像合成的线程,如绘制(impl-side),合成等逻辑在该线程中执行。

# 2.19.1. (1)单线程渲染框架

内核线程几乎包揽了页面内容渲染的所有工作,如JavaScript执行,元素布局,图层绘制,图层图像合成等,每项工作的执行耗时基本都跟页面内容相关,耗时一般在几十毫秒至几百毫秒不等。

# 2.19.2. (2)线程化渲染框架

在Chrome的线程化渲染框架中,当内核线程完成第1帧(Frame#1)的布局和记录绘制操作,立即通知合成线程对第一帧(Frame#1)进行渲染,然后内核线程就开始准备第2帧(Frame#2)的布局和记录绘制操作。由此可以看出,内核线程在进行第N+1帧的布局和记录绘制操作同时,合成线程也在努力进行第N帧的渲染并交给屏幕展示,这里利用了CPU多核的特性进行并发处理,因此提高了页面的渲染效率。由此也可知,实际上用户看到的页面内容,是上一帧的内容快照,新的一帧还在处理中。

# 2.20. 配置 Webpack 的 loader

# 2.20.1. (1)配置规则Rule的条件

Rule.test

筛选资源,符合条件的资源让这项规则中的loader处理。

除了test,还可以用

  • Rule.include(包含)
  • Rule.exclude(排除)
  • Rule.resource(可以在里面配置test、include、exclude子选项,上面三个其实是它的快捷选项)
  • Rule.issuer(匹配的不是资源路径,而是引入资源的文件的路径)
  • Rule.resourceQuery(匹配资源引入路径上从问号开始的部分)

来匹配。

这些规则可以同时使用,同时使用时,是“与”的关系。必须同时符合以上所有配置的条件才可以让这项规则中的loader处理。

# 2.20.2. (2)配置规则Rule的loader

  • Rule.use
  • Rule.loader
  1. use: ['style-loader']其实是use: [ { loader: 'style-loader'} ]的简写。
  2. loader: 'css-loader'use: [ { loader: 'css-loader'} ]的简写。

参考:『Webpack系列』—— loader配置详解 (opens new window)

# 2.21. progress-bar-webpack-plugin

webpack插件,显示构建过程进度条

# 2.22. babel-plugin-module-resolver

模块解析插件

.babelrc文件

{
  "plugins": [
    ["module-resolver", {
      "root": ["./"],
      "alias": {
         "P":"./app/p"
      }
    }]
  ]
}

因为.babelrc文件的路径在项目根, 我们指定配置module-resolver的root为"./",因此我们在使用类似import X from 'path’导入模块的使用可以不指定…/…/这种相对路径, 默认以项目根为模块的搜索路径。也可以使用别名,而不是在项目中使用相对路径。

下面举个简单的例子:

//import Mp from '../../p/MyPropTypes';
import Mp from 'P/MyPropTypes'

//import MyUtilFn from '../../../../utils/MyUtilFn';
import MyUtilFn from 'utils/MyUtilFn';

# 2.23. json-templater/string 库

可以进行简单的字符串替换

# 2.24. React Dev Tools 注入全局变量 __REACT_DEVTOOLS_GLOBAL_HOOK__

当我们使用React Dev Tools时,Dev Tools会向页面注入全局变量__REACT_DEVTOOLS_GLOBAL_HOOK__。

这个变量是连接React与Dev Tools的桥梁。

# 2.25. 简写形式:export { default } from './index.vue'

# 2.25.1. (1)转发 default

import comp from './index.vue'
export default comp

等于以下形式 :

export { default } from './index.vue'

# 2.25.2. (2)转发全部

import { foo, bar } from 'utils'
export {
  foo,
  bar
};

等于以下形式:

export * from 'utils'

# 2.26. ElementUI源码中,是如何拿到所有的自定义icon的?

ElementUI源码中,是如何拿到所有的自定义icon的?

var postcss = require('postcss');

var fontFile = fs.readFileSync(path.resolve(__dirname, 'icon.scss'), 'utf8');
var nodes = postcss.parse(fontFile).nodes;

var classList = [];

nodes.forEach((node) => {
  var selector = node.selector || '';
  var reg = new RegExp(/\.el-icon-([^:]+):before/);
  var arr = selector.match(reg);

  if (arr && arr[1]) {
    classList.push(arr[1]);
  }
});

# 2.27. 前端如何实时监听本地代码文件的修改变动?

可以使用chokidar模块

# 2.28. JS 中 replace函数

# 2.28.1. (1)replace函数

replace方法的第二个参数是函数的时候,函数的第一个参数是匹配模式的字符串。接下来的参数是与模式中的子表达式匹配的字符串,可以有 0 个或多个这样的参数。

如下面第三个例子中,camelize('abc-def-xyz')打印信息为

-d d
-x x

# 2.28.2. (2)例子

  1. 在本例中,我们将把所有的花引号替换为直引号:
name = '"a", "b"';

name.replace(/"([^"]*)"/g, "'$1'");
  1. 在本例中,我们将把字符串中所有单词的首字母都转换为大写:
name = 'aaa bbb ccc';

uw=name.replace(/\b\w+\b/g, function(word){
    return word.substring(0,1).toUpperCase() + word.substring(1);}
);
  1. 中线改驼峰
const camelizeRE = /-(\w)/g;

export function camelize(str: string): string {
  return str.replace(camelizeRE, (_, c) => {
     console.log(_, c)
     c.toUpperCase()
  });
}

# 2.29. NodeJS中如何获取换行符

var endOfLine = require('os').EOL

# 2.30. Vuepress的 displayAllHeaders 及 sidebarDepth 不生效,原因h1标题后必须接h2,不能直接跨到h3

# 2.31. Vuepress的sidebar配置数组形式时,目录的README.md如何配置?

解决:需要写成'/pmd-tools/'或者'/pmd-tools/index',写成'/pmd-tools'或者'/pmd-tools/README'无效

# 2.32. v-imapge-preview插件图片不清晰?

原因:设置的基准width太小了,同一张图片,即使用transform的scale放大,也比直接设置width为原始值模糊。解决:将基准width设置为图片原始宽度

# 2.33. VScode要使eslint-plugin-vue对vue文件生效,需要写tsconfig.json文件

# 2.34. vue-test-utils的setData不会改变引用地址?

如果组件内两个变量已经指向了同一地址,setData的方式无法让其不相等,可以用wrapper.vm.tempSelectRole = {}改变值

# 2.35. vue.mixin不生效,找不到getLocation,删除子模块的node_modules就可以了

推测是如果安装了子模块的 node_modules,vue 就会从子模块里取得,而 main.js 的 vue.mixin 会优先找当前目录下的node_modules 的 vue,所以两个 vue 引用位置不同,导致不生效。

# 2.36. 如何不改变代码,而模拟游戏内环境?

方法:修改User agent,chrome开发者工具 => 右上角... => More tools => Network Conditions => ingame...

# 2.37. touchmove事件一旦滚动,无法中途停止,即在内部调用event.preventDefault时,会报错。

解决:想模拟中断滚动,可以让元素的scrollTop始终为一定值。

# 2.38. vue 的script使用typescript时,报JSON解析错误。解决:通过排查发现是tsconfig.json中 lib 没有引入 scripthost 导致。

# 2.39. vue-lazyload重复请求图片?

原因:页面主动设置了disable-cache。解决方法:允许使用缓存。

# 2.40. React源码中用二进制表示mode

var a = 0b000
var b = 0b001
var c = 0b010
var d = 0b100

var mode = a 

// 判断是否包含模式b
mode & b // 0

// 添加模式b
mode |= b  // 1 (0b001)

// 添加模式c
mode |= c // 3 (0b011)

二进制或运算符(|):如果两个符号位都为0,则结果为0,否则为1

# 2.41. js获取上一个兄弟元素

需要用到的两个属性:previousSbiling和previousElementSibling

  1. previousSibling:获取元素的上一个兄弟节点;(既包含元素节点、文本节点、注释节点)

  2. previousElementSibling:获取上一个兄弟元素节点;(只包含元素节点);

区别如下图:

https://github.com/novlan1/technical-blog/blob/main/imgs/previousSibling.jpg

所以一般我们会用previousElementSibling

参考:js获取上一个兄弟元素 (opens new window)

# 2.42. npm查看包的版本信息

# 2.42.1. 远程包

# 2.42.1.1. (1) 查看npm服务器上所有的jquery版本信息
npm view jquery versions
# 2.42.1.2. (2) 查看jquery的最新版本是哪个
npm view jquery version
# 2.42.1.3. (3) 和第一种方式类似,也能查看jquery所有版本,但能查出更多关于jquery的信息
npm info jquery

# 2.42.2. 本地包

# 2.42.2.1. (1) 查看本地下载的jquery版本信息
npm ls jquery
# 2.42.2.2. (2) 查看全局安装的jquery
npm ls jquery -g

# 2.43. npm为什么要设置代理,设置镜像源不就行了吗,两者有什么区别?

代理的话,是设置代理服务器,代理服务器帮你转发下载请求;

镜像源的话,是镜像站点已经提前镜像(下载)了所有的npm包,你是直接从它的服务器上获取到的包。

参考: npm 代理 (opens new window)

# 2.44. 报错信息:Error 'tunneling socket' while executing npm install

解决:取消代理设置

npm config set proxy null
npm config set https-proxy null

参考:error-tunneling-socket-while-executing-npm-install (opens new window)

# 2.45. shims-vue.d.ts 解析

shims-vue.d.ts文件

declare module '*.vue' {
  import Vue from 'vue';  // 引用了type和value (value是Vue构造器 type是Vue interface)
  export default Vue;
}

在TypeScript编译器解析 import ‘aaa’ from xxx.vue 的时候

拿到 node_modules/vue/ 目录下 index.d.tsexport default出来的typevalue, type是Vue接口,value是 const Vue,这个常量的类型是VueConstructor

demo page (图中是从'vue'中import中得到的type和 value)

一个import 能同时import到两个东西,import了一种type 还import了一个value,参考这里 (opens new window)

我们来看vue.d.ts:

vue.d.ts.png

23行导出了type Vue, 129行导出了 value Vue 这两个东西都被export default出来了 当你import的时候 就拿到了这两个东西,并且改成了你import的名字。

参考:shims-vue.d.ts 解析 (opens new window)

# 2.46. Promise 版本的 JSONP

const jsonp = function (url, data) {
  return new Promise((resolve, reject) => {

    // 初始化url
    const dataString = url.indexOf('?') === -1 ? '?' : '&';
    const callbackName = `jsonpCB_${Date.now()}`;
    url += `${dataString}callback=${callbackName}`;

    if (data) {
      // 有请求参数,依次添加到url
      for (const k in data) {
        url += `&${k}=${data[k]}`;
      }
    }

    const jsNode = document.createElement('script');
    jsNode.src = url;

    // 触发callback,触发后删除js标签和绑定在window上的callback
    window[callbackName] = (result) => {
      delete window[callbackName];
      document.body.removeChild(jsNode);

      if (result) {
        resolve(result);
      } else {
        reject('没有返回数据');
      }
    };

    // js加载异常的情况
    jsNode.addEventListener('error', () => {
      delete window[callbackName];
      document.body.removeChild(jsNode);
      reject('JavaScript资源加载失败');
    }, false);

    // 添加js节点到document上时,开始请求
    document.body.appendChild(jsNode);
  });
};

jsonp('http://192.168.0.103:8081/jsonp', { a: 1, b: 'heiheihei' })
  .then((result) => {
    console.log(result);
  })
  .catch((err) => {
    console.error(err);
  }); 

参考:手写一个JSONP(promise封装) (opens new window)

# 2.47. switch 中一旦 case 匹配,就会一次执行后面的代码,直到遇到 break

# 2.47.1. 1、switch 中一旦 case 匹配,就会一次执行后面的代码,直到遇到 break

for (let i = 0;i < 3;i++) {
  switch (i) {
    case 0: {
      console.log(i);
    }
    case 2: {
      console.log(i);
    }
    default: {
      console.log(i);
    }
  }
}

上题的打印顺序是:000122

# 2.47.2. 2、无论default位置在前还是在后,都是先判断各个case, 最后才进default

switch (0) {
  default:
    text = '期待周末!';
    break;
  case 6:
    text = '今天是周六';
    break;
  case 0:
    text = '今天是周日';
}

上题中:text 为 '今天是周日'

# 2.48. JS实现滚动区域触底事件

export default {
  data(){
    return {
      isReachBottom: false,
      reachBottomDistance: 100
      scrollHeight: 0,
      offsetHeight: 0,
    }
  },
  mounted(){
    // 页面加载完成后  将高度存储起来
    let dom = document.querySelector('.comment-area .comment-list')
    this.scrollHeight = dom.scrollHeight
    this.offsetHeight = Math.ceil(dom.getBoundingClientRect().height)
  },
  methods: {
    onScroll(e) {
        let scrollTop = e.target.scrollTop
        let currentHeight = scrollTop + this.offsetHeight + this.reachBottomDistance

        if(currentHeight < this.scrollHeight && this.isReachBottom){
          this.isReachBottom = false
        }
        if(this.isReachBottom){
          return
        }
        if (currentHeight >= this.scrollHeight) {
          this.isReachBottom = true
          console.log('触底')
        }
    }
  }
}

监听滚动事件,从 event 上取得 scrollTop 信息,而不是全局的 page

参考:JS实现滚动区域触底事件 (opens new window)

# 2.49. touchmove 实时获取当前元素

// 根据坐标直接返回元素 document.elementFromPoint()

const ele = document.elementFromPoint(touch.pageX, touch.pageY);   

eg:

$('ul li').on('touchmove touchstart', (e) => {
  event.preventDefault();
  e = e || window.event;
  const touch = e.originalEvent.targetTouches[0];
  const ele = document.elementFromPoint(touch.pageX, touch.pageY);
});

参考:

  1. touchmove 实时获取当前元素 (opens new window)
  2. Document.elementFromPoint()-MDN (opens new window)

# 2.50. JS 产生正态分布随机数

有一个称为 Box-Muller (1958) (opens new window) 转换的算法能够将两个在区间(0,1] 的均匀分布转化为标准正态分布,其公式为:

y1 = sqrt( - 2 ln(u) ) cos( 2 pi v )

y2 = sqrt( - 2 ln(u) ) sin( 2 pi v )

因为三角函数计算较慢,我们可以通过上述公式的一个 polar form(极坐标形式)能够简化计算,

代码如下:

function getNumberInNormalDistribution(mean,std_dev){
    return mean+(randomNormalDistribution()*std_dev);
}

function randomNormalDistribution(){
    var u=0.0, v=0.0, w=0.0, c=0.0;
    do{
        //获得两个(-1,1)的独立随机变量
        u=Math.random()*2-1.0;
        v=Math.random()*2-1.0;
        w=u*u+v*v;
    }while(w==0.0||w>=1.0)
    //这里就是 Box-Muller转换
    c=Math.sqrt((-2*Math.log(w))/w);
    //返回2个标准正态分布的随机数,封装进一个数组返回
    //当然,因为这个函数运行较快,也可以扔掉一个
    //return [u*c,v*c];
    return u*c;
}

参考:Javascript 随机数函数 学习之二:产生服从正态分布随机数 (opens new window)

# 2.51. .npmrc文件,npm 配置文件

项目根目录下的.npmrc文件:

registry=http://registry.npmjs.org/

可以实现,仅仅对于这个项目,换npm的源了

# 2.52. NodeJS 可以直接读取 .json 文件

示例如下:

const config = require('./config.json')
console.log("name",config.name)
console.log("age",config.age)

参考:nodejs中 require 方法的加载规则 (opens new window)

# 2.53. ADB命令

ADB,全称为Android Debug Bridge,它是 Android 开发/测试人员不可替代的强大工具。

  • 查看ADB版本:adb version
  • 查看手机设备:adb devices
  • 查看设备型号:adb shell getprop ro.product.model
  • 查看电池信息:adb shell dumpsys battery
  • 查看设备ID:adb shell settings get secure android_id
  • 查看设备IMEI:adb shell dumpsys iphonesubinfo
  • 查看Android版本:adb shell getprop ro.build.version.release
  • 查看手机网络信息:adb shell ifconfig
  • 查看设备日志:adb logcat
  • 重启手机设备:adb reboot
  • 安装一个apk:adb install /path/demo.apk
  • 卸载一个apk:adb uninstall
  • 查看系统运行进程:adb shell ps
  • 查看系统磁盘情况:adb shell ls /path/
  • 手机设备截屏:adb shell screencap -p /sdcard/aa.png
  • 手机文件下载到电脑:adb pull /sdcard/aa.png ./
  • 电脑文件上传到手机:adb push aa.png /data/local/
  • 手机设备录像:adb shell screenrecord /sdcard/ab.mp4
  • 手机屏幕分辨率:adb shell wm size
  • 手机屏幕密度:adb shell wm density
  • 手机屏幕点击:adb shell input tap xvalue yvalue
  • 手机屏幕滑动:adb shell input swipe 1000 1500 200 200
  • 手机屏幕带时间滑动:adb shell input swipe 1000 1500 0 0 1000
  • 手机文本输入:adb shell input text xxxxx
  • 手机键盘事件:adb shell input keyevent xx
  • 连接多个手机设备时,指定手机设备:adb -s serialNumber

参考:MAC系统下ADB入门与简单使用 (opens new window)

# 2.54. Chrome 跨域的方式

# 2.54.1. Windows 电脑 Chrome 跨域的方式

在chrome的快捷方式上右键,在属性后面加上:

--disable-web-security --user-data-dir=C:\MyChromeDevUserData

# 2.54.2. Mac 电脑 Chrome 跨域的方式

最后文件夹为自己主目录,一进来的目录,切换zsh,用pwd可以查看

open -n /Applications/Google\ Chrome.app/ --args --disable-web-security  --user-data-dir=/Users/liyuquan/Documents/MyChromeDevUserData

参考:Windows10 Google浏览器的跨域设置——包括版本49前后两种设置 (opens new window)

# 2.55. JS字符串和 DOM 互相转化

JS字符串转DOM:

function parseDom(arg) {
    const objE = document.createElement('div');
    objE.innerHTML = arg;
    return objE.childNodes;
};

DOM转字符串:

  • 直接调用dom.innerHTML()

参考:js 字符串转dom 和dom 转字符串 (opens new window)

# 2.56. 用HTTP的方式链接远程仓库,有时会经常要求输入密码,用SSH则不用

参考:git使用指令git push origin master报错remote: Invalid username or password. fatal: Authentication failed for (opens new window)

# 2.57. tagName和nodeName的区别

首先介绍DOM里常见的三种节点类型(总共有12种,如docment):元素节点,属性节点以及文本节点,例如<h2 class="title">head</h2>,其中h2是元素节点,class是属性节点,head是文本节点,在这里你可以说h2这个元素节点包含一个属性节点和一个文本节点。其实几乎所有HTML的标签都是元素节点,而id, title, class等则是属性节点,而元素所包含的文本内容则是文本节点。

tagName和nodeName的语义是一样的,都是返回所包含标签的名称,例如上面的h2标签,都是返回h2,但是tagName只能在元素标签上使用,而nodeName则可以在所有的节点上使用。

总结:tagName只能用在元素节点上,而nodeName可以用在任何节点上,可以说nodeName涵盖了tagName,并且具有更多的功能,因此建议总是使用nodeName。

参考:tagName和nodeName的区别 (opens new window)

# 2.58. npm link的使用

  1. 假如开发的包的名称是module-a,进入module-a主目录,执行npm link
  2. 进入项目目录,执行npm link module-a

参考:npm link的使用 (opens new window)

# 2.59. node获取当前路径的三种方法

  • __dirname
  • process.cwd()
  • path.resolve(./)

区别如下:

  • __dirname:返回运行文件所在的目录
  • path.resolve('./'):当前命令所在的目录
  • process.cwd():当前命令所在的目录

参考:node获取当前路径的三种方法 (opens new window)

# 2.60. Chrome DevTools — Network - Timing:查看请求在各个阶段对应的时间

  • Queueing:浏览器会在以下情况对请求进行排队:

    • 有更高优先级的请求
    • 在这个域下,已经有6个TCP连接了,达到Chrome最大限制数量。此条规则仅适用在HTTP/1.0和HTTP/1.1
  • Stalled:Queueing中的任何一个因素发生都会导致该请求被拖延

  • Proxy negotiation:浏览器与代理服务器协商消耗的时间

  • DNS Lookup:浏览器对请求的IP地址进行DNS查找所消耗的时间

  • Initial conncection:发起连接所消耗的时间

  • Request sent:请求发送消耗的时间

  • Waiting (TTFB):浏览器等待响应的时间,TTFB表示 Time To First Byte

  • Content Download:资源下载所消耗的时间

network-timing

参考:

# 2.61. JS exec()方法:执行正则表达式匹配

在非全局模式下,exec() 方法返回的数组与 String.match() 方法返回的数组是相同的。

在全局模式下,exec() 方法与 String.match() 方法返回的结果不同。当调用 exec() 方法时,会为正则表达式对象定义 lastIndex 属性,执行执行下一次匹配的起始位置,同时返回匹配数组,与非全局模式下的数组结构相同;而 String.match() 仅返回匹配文本组成的数组,没有附加信息。

因此,在全局模式下获取完整的匹配信息只能使用 exec() 方法。

var s = "JavaScript";  //测试使用的字符串直接量
var r = /\w/g;  //匹配模式
while ((a = r.exec(s))) {  //循环执行匹配操作
    console.log("匹配文本 = " + a[0] + "    a.index = " + a.index + "    r.lastIndex = " + r.lastIndex);  //显示每次匹配后返回的数组信息
}
// 匹配文本 = J    a.index = 0    r.lastIndex = 1
// ...

参考:JS exec()方法:执行正则表达式匹配 (opens new window)

# 2.62. 【webpack】中library的作用

webpack默认打包之后的代码形式是这样的(假设我导出 module.exports = 'hello world' )

(function () {
  return 'hello world'
})()

注意:代码是一个自执行函数,外界想获取函数里面的返回值怎么办(也就是模块的导出结果 hello world ),那么就需要配置一个 library

const path = require('path')

module.exports = {
  entry: './src/utils.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    library: 'result'
  }
}

然后打包之后是这样的形式

var result = (function () {
  return 'hello world'
})()

通过把导出的结果赋值给 result 变量,配置了 library: 'result'

将打包之后的文件引入到HTML文件中,可以直接使用哦!(假设打包的文件名是bundle.js)

<body>
  <script src="./dist/bundle.js"></script>
  <script>
    console.log(result)
  </script>
</body>

如果你不想在HTML中使用,想在一个JS文件里面通过 require 导入来使用,需要这么配置

const path = require('path')

module.exports = {
  entry: './src/utils.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    libraryTarget: 'commonjs2'
  }
}

打包之后代码是这样的

module.exports = (function () {
  return 'hello world'
})()

可以在JS文件中导入来使用

import result from './bundle'

console.log(result)

同时支持import和require语法引入

两个配置同时使用,生效的是第二种模式

参考:【webpack】中library的作用 (opens new window)