Skip to main content

19 posts tagged with "javascript"

View All Tags

· One min read
const config = {
label: '',
fields: 'datas',
Component: <Component />,
rules: [
{
required: true,
message: '请选择数据',
}
],
initialValue: [],
}

这种情况下type默认是string不会校验数组,应该改成

const config = {
label: '',
fields: 'datas',
Component: <Component />,
rules: [
{
type: 'array',
required: true,
message: props.t('请选择成员'),
transform(value) {
if (value) {
return JSON.parse(value);
}

return value;
}
}
],
initialValue: [],
}

type: 'array' transform 则是校验之前转换成数组再进行校验

· One min read
  1. Promise.all 返回的也是一个promise
  2. Promise.all 其中有一个error,则直接返回reject
  3. Promise.all 是并发执行的
  4. Promise.then(value)
  • 如果value是promise, 则返回promise
  • 如果value是thenable,则在then()函数中,value准备好后,回掉两个callback
  • 否则,使用一个fulfilled状态的value作为promise返回
function PromiseAll(arr) {
return new Promise((resolve, reject) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
Promise.resolve(arr[i])
.then(res => {
result.push(res);
if (i === arr.length - 1) {
return resolve(result);
}
}).catch(err => reject(err))
}
});
};

function promise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
}

function promise2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2');
}, 1000);
});
}

const p1 = promise();
const p2 = promise2();
const test = [1, p1, p2];
PromiseAll(test).then(res => console.log('res', res)).catch(err => console.log('kkk', err));

参考链接:

MDN

面试官让我手写Promise.all

· 3 min read

在正式环境,前端代码一般做了压缩和混淆,debug比较困难,但是如果有sourceMap则更便于查找bug。

Proposed Format

{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "bar.js"],
"sourcesContent": [null, null],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
  • Line 1: The entire file is a single JSON object
  • Line 2: File version (always the first entry in the object) and must be a positive integer.
  • Line 3: An optional name of the generated code that this source map is associated with.
  • Line 4: An optional source root, useful for relocating source files on a server or removing repeated values in the “sources” entry. This value is prepended to the individual entries in the “source” field.
  • Line 5: A list of original sources used by the “mappings” entry.
  • Line 6: An optional list of source content, useful when the “source” can’t be hosted. The contents are listed in the same order as the sources in line 5. “null” may be used if some original sources should be retrieved by name.
  • Line 7: A list of symbol names used by the “mappings” entry.
  • Line 8: A string with the encoded mapping data.

The “mappings” data is broken down as follows:

  • each group representing a line in the generated file is separated by a ”;”
  • each segment is separated by a “,”
  • each segment is made up of 1,4 or 5 variable length fields.

mappings 内容表示 ";"用来区分构建后的文件中的每一行 ","用来区分构建后的文件中的每一个部分 每个部分由1,4,5个可变长度的字段构成

例如:"mappings": "AAAAA,BBBBB;CCCCC;"

AAAAA 每一位都是使用VLQ编码 从左到右分别表示:

  • 第一位,表示这个位置在(转换后的代码的)的第几列。

  • 第二位,表示这个位置属于sources属性中的哪一个文件。

  • 第三位,表示这个位置属于转换前代码的第几行。

  • 第四位,表示这个位置属于转换前代码的第几列。

  • 第五位,表示这个位置属于names属性中的哪一个变量。

index.js original

  var name = 'ssss';
var age = 'test';
console.log(name, age);

index_bundle.js generated

  console.log("ssss","test");
//# sourceMappingURL=index_bundle.js.map

index_bundle.js.map

  {
"version": 3,
"file": "index_bundle.js",
"mappings": "AAGAA,QAAQC,IAHG,OACD",
"sources": [
"webpack://webpack-test/./src/index.js"
],
"sourcesContent": [
"var name = 'ssss';\nvar age = 'test';\n\nconsole.log(name, age);"
],
"names": [
"console",
"log"
],
"sourceRoot": ""
}

利用在线网站转换source-map-visualization

"mappings": "AAGAA,QAAQC,IAHG,OACD" 转换成了

0->4:0 8->4:8 12->1:11 19->2:10

· 2 min read

问题:

  const TooltipWrap = (props) => {
const { value } = props;
if (value === 1) {
return <Tooltip type="type1" />;
}
return <Tooltip type="type2" />;
};

上述代码在入参value变化时,Tooltip是否会重新初始化? 在实际运行中会发现Tooltip只会被初始化一次 根据react的VirtualDom Diff算法

ReactElement.createElement = function(type, config, children) {
var propName;
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;

for (propName in config) {
if (hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
// ...
}
props.children = childArray;
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// ...
}
return ReactElement(
type, // 组件type
key, // 组件key
ref,
self,
source,
ReactCurrentOwner.current,
props
);
};

virtualDom diff算法 针对key和type判断是否能复用组件的逻辑

  1. key不同,重新初始化组件
  2. key相同,type不同,重新初始化组件
  3. key相同,type相同,组件可以复用

· One min read

template.md

---
title: Title
date: 2018-06-06 11:32:22
tags: []
---

# Title
#!/usr/bin/env node
const { program } = require('commander');
const fs = require('node:fs/promises');
const matter = require('gray-matter');
const moment = require('moment');
const { stringify } = require("yaml");

async function createFile() {
const filename = 'file.md';
let fh = await fs.open(filename, 'a');
const { data: frontMatter, content } = matter.read('./template.md');
const now = moment().format("YYYY-MM-DD HH:mm:ss");
frontMatter.date = now;
const newContent = `---\n${stringify(frontMatter)}---\n${content}`;
fh.write(newContent);
await fh.close();
}

program
.option('--first')
.action((commandAndOptions) => {
createFile();
if (commandAndOptions.debug) {
console.error(`Called ${commandAndOptions.name()}`);
};
});

program.parse();

const options = program.opts();
const limit = options.first ? 1 : undefined;

参考链接

· 4 min read
  一个task(宏任务) -- 队列中全部job(微任务) -- requestAnimationFrame -- 浏览器重排/重绘 -- requestIdleCallback

setTimeout

如果运行死循环函数调用setTimeout会阻塞页面运行吗?

  function loop() {
setTimeout(loop, 0);
}

loop();

答案是不会阻塞页面正常运行,因为主线程会不断创建宏任务放进宏任务队列里面,而主线程都只会执行一个宏任务,然后进入下一次循环

requestAnimationFrame

  button.addEventListener('click', () => {
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
box.style.display = 'block';
box.style.display = 'none';
});

主线程会执行修改box的样式,但是渲染的时候只有最后 box.style.display = 'none' 会生效

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';
box.style.transform = 'translateX(500px)';
})

浏览器渲染时会忽略初始值 translateX(1000px),动画会从0-500px执行过渡动画。

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';

requestAnimationFrame(() => {
box.style.transform = 'translateX(500px)';
});
})

因为requestAnimationFrame会在浏览器渲染之前执行,所以动画还是会从0-500px 执行

  button.addEventListener('click', () => {
box.style.transform = 'translateX(1000px)';
box.style.transition = 'transform 1s ease-in-out';

requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = 'translateX(500px)';
})
});
})

只有在第二帧修改translate,动画才会从 1000px-500px执行

Animation callbacks

Animation callbacks队列会一次性执行完成,执行过程中新增的任务会放到下一帧去执行。

microtasks

  for (let i=0; i < 100; i++) {
const span = document.createElement('span');
document.body.appendChild(span);
span.textContent = 'Hello';
}

document.body.appendChild(span); 会向微任务队列提供100条消息 span.textContent = 'Hello'; 会向微任务队列提供100条消息 所以一共会有200条微任务被添加

微任务执行过程会不断运行,直到所有微任务清空。 如果微任务执行过程中,不断新增微任务,会不断执行微任务,直到清空微任务队列,会阻塞后面线程任务。

promise

  button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 1'));
console.log('Listener 1');
});

button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 2'));
console.log('Listener 2');
});

在页面上点击后 打印顺序是 Listener 1 => Microtask 1 => Listener 2 => Microtask 2

第一次 js stack 放入 Listener 1 执行输出 Listener 1 然后执行微任务 Microtask 1 第二次 js stack 放入 Listener 2 执行输出 Listener 2 然后执行微任务 Microtask 2

  button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 1'));
console.log('Listener 1');
});

button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('Microtask 2'));
console.log('Listener 2');
});

button.click();

用js执行点击 在页面上点击后 打印顺序是 Listener 1 => Listener 2 => Microtask 1 => Microtask 2

这里会同步派发button的点击事件执行Listener 1 => Listener 2,接下来才会执行微任务

参考视频链接:Jake Archibald: 在循环 - JSConf.Asia

· One min read

题目

const obj = { obj: { name: 123 }, target: [{a: 111}], };

使用字符串 'obj.target[0].a' 读取value

分析

题目的关键点是考察从字符串中读取关键词 可以使用正则匹配实现

const str = 'target[0].a';
const arr = str.split('.');
let params = [];
for (let i=0; i< arr.length; i++) {
const arrMatch = arr[i].match(/\[\d+\]/);
if (!!arrMatch) {
const numStr = arrMatch[0];
const index = arrMatch.index;
params.push(arrMatch.input.slice(0, index));
params.push(numStr.match(/\d+/)[0]);
} else {
params.push(arr[i]);
}
}

console.log(params);

· 2 min read

http://datagenetics.com/blog/july22012/index.html

C++

#include <iostream>
#include <vector>
#include <string>
#include<algorithm>

using namespace std;

const int Floors = 25;
const int Eggs = 5;

// max{ fn{egg - 1, dropFloor} fn{egg, floor - dropFloor} }

// 一共egg个鸡蛋, floor层, 从dropFloor层扔, dropFloor 取值 1 - n
int drop(int egg, int floor) {
if (egg == 0 || floor == 0) {
printf("egg %d and floor %d: %d \n", egg, floor, 0);
return 0;
}

if (egg == 1) {
printf("egg %d and floor %d: %d \n", egg, floor, floor);
return floor;
}

if (floor == 1) {
printf("egg %d and floor %d: %d \n", egg, floor, 1);
return 1;
}

int minRes = INT_MAX;
for (int i = 1; i < floor; i++)
{
int curRes = max(drop(egg - 1, i - 1), drop(egg, floor - i));
if (minRes > curRes) {
minRes = curRes;
}
}

printf("egg %d and floor %d: %d \n", egg, floor, minRes + 1);
return minRes + 1;
}

int main()
{
int msg = drop(Eggs, Floors);

cout << msg << endl;
}

运行时间: 6.8512s

javascript

const Floors = 25;
const Eggs = 5;

// max{ fn{egg - 1, dropFloor} fn{egg, floor - dropFloor} }

// 一共egg个鸡蛋, floor层, 从dropFloor层扔
function drop(egg, floor) {
if (egg == 0 || floor == 0) {
return 0;
}

if (egg == 1) {
return floor;
}

if (floor == 1) {
return 1;
}

let minRes = Infinity;
for (let i = 1; i < floor; i++)
{
let curRes = Math.max(drop(egg - 1, i - 1), drop(egg, floor - i));
if (minRes > curRes) {
minRes = curRes;
}
}

return minRes + 1;
}

console.log(drop(Eggs, Floors));

运行时间: 6.022s

思路:F层E个鸡蛋 可以拆解成 假设从k层仍一个鸡蛋 Max{ k层E-1个鸡蛋, F-k层E个鸡蛋 }

k为最优解 可以用遍历的方式 k从 1 - F 层取值 Min{ (1 - F) 层扔一个鸡蛋 } 带入上面 递归计算。

· 4 min read

组件如何实现数据双向绑定?

  <template>
<div :class="wrapClasses">
<template v-if="type !== 'textarea'">
<div :class="[prefixCls + '-group-prepend']" v-if="prepend" v-show="slotReady"><slot name="prepend"></slot></div>
<i class="ivu-icon" :class="['ivu-icon-ios-close-circle', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable && currentValue" @click="handleClear"></i>
<i class="ivu-icon" :class="['ivu-icon-' + icon, prefixCls + '-icon', prefixCls + '-icon-normal']" v-else-if="icon" @click="handleIconClick"></i>
<i class="ivu-icon ivu-icon-ios-search" :class="[prefixCls + '-icon', prefixCls + '-icon-normal', prefixCls + '-search-icon']" v-else-if="search && enterButton === false" @click="handleSearch"></i>
<span class="ivu-input-suffix" v-else-if="showSuffix"><slot name="suffix"><i class="ivu-icon" :class="['ivu-icon-' + suffix]" v-if="suffix"></i></slot></span>
<transition name="fade">
<i class="ivu-icon ivu-icon-ios-loading ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
</transition>
<input
:id="elementId"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
ref="input"
:type="type"
:class="inputClasses"
:placeholder="placeholder"
:disabled="disabled"
:maxlength="maxlength"
:readonly="readonly"
:name="name"
:value="currentValue"
:number="number"
:autofocus="autofocus"
@keyup.enter="handleEnter"
@keyup="handleKeyup"
@keypress="handleKeypress"
@keydown="handleKeydown"
@focus="handleFocus"
@blur="handleBlur"
@input="handleInput"
@change="handleChange">
<div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady"><slot name="append"></slot></div>
<div :class="[prefixCls + '-group-append', prefixCls + '-search']" v-else-if="search && enterButton" @click="handleSearch">
<i class="ivu-icon ivu-icon-ios-search" v-if="enterButton === true"></i>
<template v-else>{{ enterButton }}</template>
</div>
<span class="ivu-input-prefix" v-else-if="showPrefix"><slot name="prefix"><i class="ivu-icon" :class="['ivu-icon-' + prefix]" v-if="prefix"></i></slot></span>
</template>
<textarea
v-else
:id="elementId"
:wrap="wrap"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
ref="textarea"
:class="textareaClasses"
:style="textareaStyles"
:placeholder="placeholder"
:disabled="disabled"
:rows="rows"
:maxlength="maxlength"
:readonly="readonly"
:name="name"
:value="currentValue"
:autofocus="autofocus"
@keyup.enter="handleEnter"
@keyup="handleKeyup"
@keypress="handleKeypress"
@keydown="handleKeydown"
@focus="handleFocus"
@blur="handleBlur"
@input="handleInput">
</textarea>
</div>
</template>
<script>
import { oneOf, findComponentUpward } from '../../utils/assist';
import calcTextareaHeight from '../../utils/calcTextareaHeight';
import Emitter from '../../mixins/emitter';
const prefixCls = 'ivu-input';
export default {
name: 'Input',
mixins: [ Emitter ],
props: {
type: {
validator (value) {
return oneOf(value, ['text', 'textarea', 'password', 'url', 'email', 'date']);
},
default: 'text'
},
value: {
type: [String, Number],
default: ''
},
size: {
validator (value) {
return oneOf(value, ['small', 'large', 'default']);
},
default () {
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
}
},
placeholder: {
type: String,
default: ''
},
maxlength: {
type: Number
},
disabled: {
type: Boolean,
default: false
},
icon: String,
autosize: {
type: [Boolean, Object],
default: false
},
rows: {
type: Number,
default: 2
},
readonly: {
type: Boolean,
default: false
},
name: {
type: String
},
number: {
type: Boolean,
default: false
},
autofocus: {
type: Boolean,
default: false
},
spellcheck: {
type: Boolean,
default: false
},
autocomplete: {
validator (value) {
return oneOf(value, ['on', 'off']);
},
default: 'off'
},
clearable: {
type: Boolean,
default: false
},
elementId: {
type: String
},
wrap: {
validator (value) {
return oneOf(value, ['hard', 'soft']);
},
default: 'soft'
},
prefix: {
type: String,
default: ''
},
suffix: {
type: String,
default: ''
},
search: {
type: Boolean,
default: false
},
enterButton: {
type: [Boolean, String],
default: false
}
},
data () {
return {
currentValue: this.value,
prefixCls: prefixCls,
prepend: true,
append: true,
slotReady: false,
textareaStyles: {},
showPrefix: false,
showSuffix: false
};
},
computed: {
wrapClasses () {
return [
`${prefixCls}-wrapper`,
{
[`${prefixCls}-wrapper-${this.size}`]: !!this.size,
[`${prefixCls}-type`]: this.type,
[`${prefixCls}-group`]: this.prepend || this.append || (this.search && this.enterButton),
[`${prefixCls}-group-${this.size}`]: (this.prepend || this.append || (this.search && this.enterButton)) && !!this.size,
[`${prefixCls}-group-with-prepend`]: this.prepend,
[`${prefixCls}-group-with-append`]: this.append || (this.search && this.enterButton),
[`${prefixCls}-hide-icon`]: this.append, // #554
[`${prefixCls}-with-search`]: (this.search && this.enterButton)
}
];
},
inputClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size,
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-with-prefix`]: this.showPrefix,
[`${prefixCls}-with-suffix`]: this.showSuffix || (this.search && this.enterButton === false)
}
];
},
textareaClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-disabled`]: this.disabled
}
];
}
},
methods: {
handleEnter (event) {
this.$emit('on-enter', event);
if (this.search) this.$emit('on-search', this.currentValue);
},
handleKeydown (event) {
this.$emit('on-keydown', event);
},
handleKeypress(event) {
this.$emit('on-keypress', event);
},
handleKeyup (event) {
this.$emit('on-keyup', event);
},
handleIconClick (event) {
this.$emit('on-click', event);
},
handleFocus (event) {
this.$emit('on-focus', event);
},
handleBlur (event) {
this.$emit('on-blur', event);
if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
this.dispatch('FormItem', 'on-form-blur', this.currentValue);
}
},
handleInput (event) {
let value = event.target.value;
if (this.number && value !== '') value = Number.isNaN(Number(value)) ? value : Number(value);
this.$emit('input', value);
this.setCurrentValue(value);
this.$emit('on-change', event);
},
handleChange (event) {
this.$emit('on-input-change', event);
},
setCurrentValue (value) {
if (value === this.currentValue) return;
this.$nextTick(() => {
this.resizeTextarea();
});
this.currentValue = value;
if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
this.dispatch('FormItem', 'on-form-change', value);
}
},
resizeTextarea () {
const autosize = this.autosize;
if (!autosize || this.type !== 'textarea') {
return false;
}
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
},
focus () {
if (this.type === 'textarea') {
this.$refs.textarea.focus();
} else {
this.$refs.input.focus();
}
},
blur () {
if (this.type === 'textarea') {
this.$refs.textarea.blur();
} else {
this.$refs.input.blur();
}
},
handleClear () {
const e = { target: { value: '' } };
this.$emit('input', '');
this.setCurrentValue('');
this.$emit('on-change', e);
},
handleSearch () {
if (this.disabled) return false;
this.$refs.input.focus();
this.$emit('on-search', this.currentValue);
}
},
watch: {
value (val) {
this.setCurrentValue(val);
}
},
mounted () {
if (this.type !== 'textarea') {
this.prepend = this.$slots.prepend !== undefined;
this.append = this.$slots.append !== undefined;
this.showPrefix = this.prefix !== '' || this.$slots.prefix !== undefined;
this.showSuffix = this.suffix !== '' || this.$slots.suffix !== undefined;
} else {
this.prepend = false;
this.append = false;
}
this.slotReady = true;
this.resizeTextarea();
}
};
</script>

· 3 min read

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

Cookie主要用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

JavaScript通过Document.cookies访问Cookie

    console.log(document.cookie); 

sessionStorage 对象

sessionStorage 属性允许你访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。在新标签或窗口打开一个页面会初始化一个新的会话,这点和 session cookies 的运行方式不同。

语法

    // 保存数据到sessionStorage
sessionStorage.setItem('key', 'value');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('key');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('key');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();

localStorage 对象

localStorage 与 sessionStorage 一样,都遵循同源策略,但是它是持续存在的。localStorage 首次出现于 Firefox 3.5。

localStorage 和 globalStorage[location.hostname] 是一样的, 除了作用域是在 HTML5 origin (结构 + 主机名 + 非标准的端口), 并且 localStorage 是一个 Storage 实例 ,而globalStorage[location.hostname] 是一个 StorageObsolete 实例

IndexedDB

IndexedDB 是一个用于在浏览器中储存较大数据结构的 Web API, 并提供索引功能以实现高性能查找. 像其他基于 SQL 的 关系型数据库管理系统 (RDBMS) 一样, IndexedDB 是一个事务型的数据库系统. 然而, 它是使用 JavaScript 对象而非列数固定的表格来储存数据的.