楔子
作为一名拥有钢铁般意志的前端打工人,装逼是不可能的,这辈子都不可能装逼。如果真要装逼,那就大家一起装逼,毕竟前端要讲武德嘛,要耗子尾汁。遂决定写下前端装逼技巧108式,供诸君茶余饭后一乐,时不时秀个骚操作,为打工的生活增添一抹亮色。
因作为打工人,时间、精力有限,目前大纲只有约50式,还请诸君有好的装逼要点私信或者在评论区留言,也可在我的博客页面扫码添加微信,大家共同探讨装逼大计、共同迎接打工人的光明未来!
系列文章力求通过一行代码或者一个小的切入点去理解一个知识点,文章风格所限,引用资料部分,将在对应小节末尾标出。
第一式:子曰,公欲装逼好,工具少不了
- 代码太丑陋,carbon来相救:把你的代码转换为精美图片进行分享(点击图片跳转)
本文为便于代码复制,将奉行不首先装逼原则,尽量减少此装逼利器的使用。
第二式:console
调试万金油,学会开车更上头
console.log()
在前端调试中的地位自不必赘述,其实一代车神也对其五体投地,不信诸君细看(如真有不解其意者,建议发扬不耻下问的求知精神,问问你旁边的同事):
是的,以上图片是由console.log()
完成的,我没有骗你,贴出代码以证清白,为便于诸君控制台开车,此处我们忘掉第一式:
// 在此提醒,为免于生成丑陋的锯齿背景图片,请注意空格的个数,并保证console面板的宽度。
console.log(`%c
%c FBI WARNING %c
%c Federal Law provides severe civil and criminal penalties for
the unauthorized reproduction,distribution, or exhibition of
copyrighted motion pictures (Title 17, United States Code,
Sections 501 and 508). The Federal Bureau of Investigation
investigates allegations of criminal copyright infringement
(Title 17, United States Code, Section 506).
`,
'background: #000; font-size: 18px; font-family: monospace',
'background: #f33; font-size: 18px; font-family: monospace; color: #eee; text-shadow:0 0 1px #fff',
'background: #000; font-size: 18px; font-family: monospace',
'background: #000; font-size: 18px; font-family: monospace; color: #ddd; text-shadow:0 0 2px #fff'
)
为什么会这样呢?想必你还记得其他语言中的print()
。占位符是print()
的专属吗?不,他们在console.log()
中同样适用:
%s
:字符串%d
:整数%i
:整数%f
:浮点数%o
:obj对象(DOM)%O
:obj对象%c
:CSS样式
console.log()
可以通过以上这些特有的占位符进行信息的加工输出。是的,你可能已经明白,上面代码的玄机就在四个%c
,第一个创建神秘而性感的纯黑背景;第二个给“FBI WARNING”加上红色的背景;第三个恢复纯黑的性感;第四个配上白色的文字,如此,大事已成。
明白了以上原理,诸君就可以自由发挥,展示你们强大的css实力了,甚至还可以输出gif背景图,在装逼的路上更上几层楼。不装了,我是css渣渣。
console.log(
'%c孤篷',
'text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);font-size:5em'
)
那么,我们是否可以超越度娘,在自家公司官网控制台完成精美的招聘文案投送呢?
拓展:console
对象都有哪些方法?
参考资料: 小蝌蚪日记:通过console.log高仿FBI Warning | Using the F12 Tools Console to View Errors and Status
第三式:芙蓉面,杨柳腰,无物比妖娆 —— 让你看清UI的轮廓
UI轮廓哪里寻,
outline
属性来帮您。html * { outline: 1px solid red }
解析与思考
- 这里没有使用 border 的原因是 border 会增加元素的大小但是 outline 不会;
- 通过这个技巧不仅能帮助我们在开发中迅速了解元素所在的位置,还能帮助我们方便地查看任意网站的布局;
- 所有浏览器都支持 outline 属性;outline (轮廓)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用;
- 轮廓线不会占据空间,也不一定是矩形(比如2D转换等)。
去掉Chrome浏览器中输入框以及其它表单控件获得焦点时的带颜色边框
input { outline: none; }
通过一个开关实现任意网页开启关闭outline
- Chrome右上角三个点⇒书签⇒书签管理器⇒右上角三个点⇒「添加新书签」;
- 名称随意,粘贴以下代码到网址中;
然后我们就可以在任意网站上点击刚才创建的书签,内部会判断是否存在调试的 style。存在的话就删除,不存在的话就添加,通过这种方式我们就能很方便的通过这个技巧查看任意网页的布局了。
javascript: (function() { var elements = document.body.getElementsByTagName('*'); var items = []; for (var i = 0; i < elements.length; i++) { if (elements[i].innerHTML.indexOf('html * { outline: 1px solid red }') != -1) { items.push(elements[i]); } } if (items.length > 0) { for (var i = 0; i < items.length; i++) { items[i].innerHTML = ''; } } else { document.body.innerHTML += ''; } })();
参考资料: 很好用的 UI 调试技巧
第四式:角声寒,夜阑珊,又改需求。难,难,难!—— 类型转换助你不带脏字的骂产品、优雅的夸自己
(!(~+[])+{})[--[~+""][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]*~+[]]]
:sb([][[]]+[])[+!![]]+([]+{})[!+[]+!![]]
:nb(+!![]*([]+{})+[]+{})[+[]]+([]+{})[!+[]+!![]]
:Nb
图解:取类型转换得到的字符串里的字母进行拼凑(看懂了原理,其实我们完全可以尝试写的更简练一些)
插件: zhuangbility,一个可以逆向操作,输入文字,返回操作符的npm插件
第五式:a == 1 && a == 2 && a == 3
,那你可以实现a === 1 && a === 2 && a === 3
吗?
a == 1 && a == 2 && a == 3
:// 当然,你也可以把count作为属性放在a对象上 let count = 1 let a = { valueOf: function(){return count++} } console.log(a==1 && a==2 && a==3) // true
- 对象在转换基本类型时,会调用该对象上
valueOf
或toString
这两个方法,该方法的返回值是转换为基本类型的结果 - 具体调用以上哪个方法取决于内置的
toPrimitive
调用结果
- 对象在转换基本类型时,会调用该对象上
a === 1 && a === 2 && a === 3
:
let count = 1;
Object.defineProperty(window, 'a', {
get: function() {
return count ++;
}
});
console.log(a===1 && a===2 && a===3) // true
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。同时,该API也是Vue 2.x数据绑定实现的核心,Vue 在 3.x 版本之后改用 Proxy 进行实现,本系列文章后续会进行简单讨论。原理可参考:译:在JS中,如何让(a===1 && a===2 && a === 3)(严格相等)的值为true? | 深入浅出Object.defineProperty() | ECMAScript7规范中的ToPrimitive抽象操作
第六式:最近有点儿火的 Web Components 可能并不是小鲜肉
Web Components原理:
- html很宽松,浏览器也可以识别不规则、不合法标签(元素)(如
会展示"Web Components"。);Web Components - 自定义继承自
HTMLElement
的类,称为自定义元素的类; - 经过
window.customElements.define
API定义和注册自定义元素,使得不合法标签(自定义元素)与自定义元素的类关联,实现合法化; - 通过模板标签
简化类的定义过程并添加样式;
- 通过自定义元素的
attachShadow()
方法开启 Shadow DOM(这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部),隐藏自定义元素的内部实现; - 添加事件监听、进行组件化封装等。
参考资料: Web Components 入门实例教程-阮一峰 | Window.customElements
第七式:Windows环境变量设置其实可以很简单
使用Windows系统电脑进行开发的小伙伴也许经常会碰到需要手动设置环境变量的情况,其实设置环境变量也可以很简单的通过命令行完成:
# 查看当前所有可用的环境变量
set
# 查看某个环境变量:查看path变量的值
set path
# 修改环境变量(注意:这里是覆盖)
set 变量名=变量内容
# 设置为空
set 变量名=
# 给变量追加内容(%变量名%;代表以前的值)
set 变量名=%变量名%;变量内容
# 将C:Gobin添加到path中
set path=%path%;C:Gobin
参考资料: Windows使用cmd命令行查看、修改、删除与添加环境变量
第八式:1.toFixed()
、1.0.toFixed()
和1..toFixed()
,究竟哪个写法是对的?
在数字字面量中,1.xxxxx
这样的语法是浮点数表示法。所以1.toFixed()
这样的语法在 JavaScript 中会报错,这个错误来自于浮点数的字面量解析过程,而不是“.作为存取运算符”的处理过程。在 JavaScript 中,浮点数的小位数是可以为空的,因此“1.”和“1.0”将作为相同的浮点数被解析出来。所以会出现:
1. === 1; // true
1. === 1.0; // true
1 === 1.0; // true
1.; // 1
1.0; // 1
既然“1.”表示的是浮点数,那么“1..toFixed”表示的就是该浮点数字面量的“.toFixed”属性。当是数字字面量时,可通过类似Number(1).toFixed()
创建基本包装类型(显示装箱),然后就可以进行属性和方法的添加、读取(或者可借助小括号把字面量括起来,告诉浏览器引擎这是一个整体)。
- 装箱:将基本数据类型转换为对应的引用类型的操作(装箱又分为隐式装箱和显式装箱);
- 拆箱:把引用类型转换成基本数据类型。
基本类型不能有属性和方法,当给它们添加属性的时候系统会自动进行包装类并销毁:
var num = 1;
num.len = 2;
//上一行代码会产生如下过程:
// new Number(1).len =2;
// delete len;
// 也就是会先添加len属性,当前语句执行结束后即销毁,所以下一行打印num还是1,没有len属性。
console.log(num, num.len);//1 undefined
var num = new Number(1);
num.len = 2;
console.log(num); // Number {1, len: 2}
参考拓展: 谈谈JavaScript中装箱和拆箱
第九式:typeof不靠谱,我们又该如何判断类型?
typeof
之殇:我们应该都知道,使用typeof
可以准确判断除null
以外的基本类型,以及function
、symbol
类型;null
会被typeof
判断为object
。- 在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object";
- 在 ES 6 之前,typeof 总能保证对任何所给的操作数返回一个字符串。即便是没有声明的标识符,typeof 也能返回 'undefined'。使用 typeof 永远不会抛出错误。但在加入了块级作用域的 let 和 const 之后,在其被声明之前对块中的 let 和 const 变量使用 typeof 会抛出一个 ReferenceError。块作用域变量在块的头部处于“暂存死区”,直至其被初始化,在这期间,访问变量将会引发错误。
以前经常拿来判断数组的
instanceof
是怎么实现的:使用a instanceof B
判断的是a 是否为 B 的实例,即 a 的原型链上是否存在 B 构造函数(ES6之后可以通过Array.isArray()
来判断是否是数组)。// L 表示左表达式,R 表示右表达式 const customInstanceof = (L, R) => { if (typeof L !== 'object') return false while (true) { // 已经遍历到了最顶端 if (L === null) return false // 利用原型链进行判断 if (R.prototype === L.__proto__) return true L = L.__proto__ } }; customInstanceof([], Array) // true
constructor为什么不是我们的选择?
- constructor属性是可以被修改的,会导致检测出的结果不正确;
除了
undefined
和null
,其他类型的变量均能使用constructor
判断出类型。let bool=true; bool.constructor==Boolean //true let num1=1; num1.constructor==Number //true let num2=new Number(); num2.constructor==Number //true // constructor属性是可以被修改的 num2.constructor = Object num2.constructor==Number //false let str='hello world'; str.constructor==String //true
Object.prototype.toString竟如此万能?
Object.prototype.toString.call(123) //"[object Number]" Object.prototype.toString.call('str') //"[object String]" Object.prototype.toString.call(true) //"[object Boolean]" Object.prototype.toString.call({}) //"[object Object]" Object.prototype.toString.call([]) //"[object Array]" // 定义getType方法,用来判断类型 getType = (obj) => { return Object.prototype.toString.call(obj).slice(8, -1) } getType(12n) // BigInt getType(Symbol()) // Symbol getType(() => {}) // Function getType() // Undefined getType(null) // Null getType(NaN) // Number
资料参考: typeof | The history of “typeof null”
第十式:十进制二进制互转,真的不用那么麻烦
使用
NumberObject.toString(radix)
十进制转二进制:// 如有补齐位数的需求,可通过判断返回值的长度在前面加0 let num = 10; console.log(num.toString(2)); // 1010
使用
parseInt(string, radix);
二进制转十进制:let num = 1010101; console.log(parseInt(num,2)); // 85
- Tips:由于以上代码都使用let定义了num变量,除了刷新页面外,该如何在控制台分别执行呢?只需把代码放在一对花括号之间即可(块级作用域)。
第十一式:没有加减乘除,如何比较正整数字符串的大小?
在接手的部分项目中,存在需要前端拼接Elasticsearch查询语句的情况,好不容易会了点Elasticsearch,却发现问题并没有那么简单:金额数量区间查询你告诉我存储的是字符串?那岂不是会出现1<3000<5
的情况?天啦噜,不要逗我好吗?
那么,在不改动ES的情况下,如何通过正则表达式查询来实现正整数字符串大小的比较呢?直接说思路:数位更多或者从高位开始比,数值更大即是更大的数【一时间没想到更好的解法,有更好的解法欢迎留言或者私信】。
// 通过正则表达式从字符串数组中筛选出大于某个数值的字符串类型数据
const filterStrNumberByRegExp = (num, arr) => {
const strBaseNumber = num.toString();
const arrBaseNumber = strBaseNumber.split('');
const len = strBaseNumber.length;
// 生成正则:数位更多或者从高位开始比,数值更大
let strRegExp = `d{${len+1}}`;
arrBaseNumber.map((item, index) => {
// 这里因为有位数限制,'^'和'$'不是必须的,可以去除
strRegExp += `|${strBaseNumber.substring(index,-1) || '^'}[${+item + 1}-9]d{${len - index - 1}}$`
});
// 丢给ES进行查询时,貌似不可使用d(可用[0-9]替代)、开头、结尾匹配等字符,上面四行可用下面注释内容替换
//let strRegExp = `[0-9]{${len+1}}`;
//arrBaseNumber.map((item, index) => {
// strRegExp += `|${strBaseNumber.substring(index,-1) || ''}[${+item + 1}-9][0-9]{${len - index - 1}}`
//});
const regExp = new RegExp(strRegExp);
// 丢给ES进行正则查询时使用strRegExp结果
console.log(regExp, strRegExp);
return arr.filter(item => {
// 小于等于判断的话,这里取反或者自行修改正则
if(regExp.test(item)) return true;
});
};
filterStrNumberByRegExp(386, ['12', '334', '556', '1122', '5546','234','388','387','1234','386','385']); // ["556", "1122", "5546", "388", "387", "1234"]
详细Elasticsearch列表页搜索公共方法实现可以查看我的这篇笔记。
第十二式:前端太寂寞?如何让页面和你说话? —— TTS(Text To Speah)
在项目中需要对ajax请求返回的消息进行语音播报,str 为需要播报的信息(适应于错误信息语音提示等场景):
//语音播报
function voiceAnnouncements(str){
// 百度语音合成:或者使用新版地址https://tsn.baidu.com/text2audio
var url = "http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=5&text=" + encodeURI(str);
var n = new Audio(url);
n.src = url;
n.play();
};
voiceAnnouncements(`
秋名山上路人稀,常有车手较高低;
如今车道依旧在,不见当年老司机。
司机车技今尚好,前端群里不寂寥;
向天再借五百年,誓言各行领风骚。
`);
// 尝试了一些换女声的方式,但是,都失败了。。。
voiceAnnouncements(`
哇,代码写的真棒,你可真秀哇!
`);
参数解释:
- lan:固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh
- ie:编码方式
- spd:语速,取值0-9,默认为5中语速
- text:合成的文本,使用UTF-8编码。小于512个中文字或者英文数字。(文本在百度服务器内转换为GBK后,长度必须小于1024字节)
- React Native Text-To-Speech library for Android and iOS
- 用语音控制自己的网站 annyang:A tiny JavaScript Speech Recognition library that lets your users control your site with voice commands.annyang has no dependencies, weighs just 2 KB, and is free to use and modify under the MIT license。
第十三式:失焦事件与点击事件冲突怎么办?
场景:
- 下拉框中blur与click冲突;
- 输入框blur与下方可点击浮沉click冲突:输入值时下方出现浮层,输入框失去焦点时,浮层隐藏;点击浮层条目触发搜索并隐藏浮层;
- 问题:点击浮层时,由于失焦事件先触发,浮层隐藏逻辑执行,导致浮层的onClick事件逻辑无法执行
// 点击弹窗条目进行搜索
handleSearch = (activeSearch) => {
console.log(activeSearch);
this.setState({ visible: false });
}
// 获得焦点,有值时展示弹窗
onFocus = () => {
if (this.state.keyword) {
this.setState({ visible: true });
}
}
// 输入且有值时展示弹窗
onChange = (e) => {
this.setState({
keyword: e.target.value,
visible: !!e.target.value
})
}
// 失去焦点隐藏弹窗
onBlur = () => {
if (this.state.keyword) {
this.setState({ visible: false });
}
}
render() {
const { keyword, visible } = this.state;
return (
}
placeholder="支持ID、名称、主邮箱、客户经理、专属账户、客户ID、GroupID搜索"
style={ { width: 460 } }
onFocus={this.onFocus}
onChange={this.onChange}
onBlur={this.onBlur}
/>
{
// 展示弹窗(点击条目完成搜索)
visible && keyword &&
{
showOptions.map(item => (
this.handleSearch(item)}
className={styles.item}
key={item.key}
>
{item.label}:{keyword}
))
}
}
);
}
解决:
方法一:给失焦事件设置延迟触发
onBlur = () => { if (this.state.keyword) { setTimeout(() => { this.setState({ visible: false }); }, 300); } }
方法二:使用onMouseDown替代onClick
- mousedown事件:当鼠标指针移动到元素上方,并按下鼠标按键时,会发生mousedown事件,所以它会先于失焦事件执行。
- mouseup事件:当在元素上放松鼠标按钮时,会发生mouseup事件。
第十四式:不用加减乘除如何做加法——位运算让你的代码更高效
- JavaScript 位运算符
位运算是基于二进制的,如何快速获得二进制可参考第十式。
不用加减乘除做加法
function add(a,b) { let sum; let add1; while(b!=0) { // 异或 sum = a^b; // 与 左移 add1 = (a&b)<<1; a = sum; b = add1; } return a }; add(1,2); // 3
JS按位运算符的妙用:
使用
&
运算符判断一个数的奇偶(只需记住0和1与1进行&
运算的结果即可):偶数 & 1 = 0
奇数 & 1 = 1
使用
~~,>>,<<,>>>,|
来取整:~~Math.PI
:3(按位取反再取反)Math.PI>>0
,Math.PI<<0
,Math.PI>>>0
:3(按位左移或者右移0位,>>>
不可用于负数)Math.PI|0
:3,按位异或
使用
<<,>>
来计算乘除:- 整数左移n位相当于乘2的n次方;
- 右移相当于除以2的n次方,再向下取整
- 利用
^
来完成比较两个数是否相等:!(a ^ b)
- 使用
^
来完成值交换:参考第十五式 使用
&,>>,|
来完成rgb值和16进制颜色值之间的转换16进制颜色值转RGB:
function hexToRGB(hex){ hex = hex.replace("#","0x"); let r = hex >> 16; let g = hex >> 8 & 0xff; let b = hex & 0xff; return "rgb("+r+","+g+","+b+")"; }; hexToRGB('#cccccc'); // rgb(204,204,204)
RGB转16进制颜色值:
function RGBToHex(rgb){ let rgbArr = rgb.split(/[^d]+/); let color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3]; return "#"+color.toString(16); }; RGBToHex('rgb(204,204,204)'); // #cccccc
参考资料: JavaScript 位运算符
第十五式:无聊的脑筋急转弯,不借助第三个变量交换a,b两个变量值的N种方法
方法一:加减
a = a + b; b = a - b; a= a - b;
方法二:位运算
a ^= b; b ^= a; a ^= b;
方法三:对象或者数组
a = {a, b}; b = a.a; a = a.b; // a = [a, b]; // b = a[0]; // a = a[1];
方法四:ES 6 解构赋值
[a, b] = [b, a]
方案五:运算符优先级
a = [b, b=a][0];
参考资料: 不借助第三个变量交换a,b两个变量值
第十六式:如何在浏览器当前页面打开并操作另一个tab页
if (window.customeWindow) {
window.customeWindow.close()
}
window.customeWindow = window.open()
window.customeWindow.document.write('写点什么呢?
')
window.customeWindow.document.write('
想写什么就写什么。
')
window.customeWindow.document.write('再追加点别的。')
window.customeWindow.document.close() // 连续追加输入结束
window.customeWindow.document.write('哈哈,现在页面上就只有我了!')
window.customeWindow.document.write('
不,还有我!
')
参考资料: BRAFT EDITOR富文本编辑器预览
第十七式:产品说要按照中文拼音顺序排序?
- 使用
stringObject.localeCompare(target)
方法实现中文按照拼音顺序排序
var array = ["上海", "北京", "杭州", "广东", "深圳", "西安"];
// localeCompare() 方法返回一个数字来指示一个参考字符串是否在排序顺序前面或之后或与给定字符串相同。
array = array.sort((item1, item2) => item1.localeCompare(item2));
// ["北京", "广东", "杭州", "上海", "深圳", "西安"]
参考资料: String.prototype.localeCompare()
- 一个对象数组按照另一个数组排序
sortFunc = (propName, referArr) => (prev, next) =>
referArr.indexOf(prev[propName]) - referArr.indexOf(next[propName])
// 按照年龄age的顺序给person排序
const age = [33, 11, 55, 22, 66];
const person = [
{age: 55, weight: 50},
{age: 22, weight: 42},
{age: 11, weight: 15},
{age: 66, weight: 56},
{age: 33, weight: 68}]
person.sort(sortFunc('age', age));
// 结果:
// [
// {"age": 33,"weight": 68},
// {"age": 11,"weight": 15},
// {"age": 55,"weight": 50},
// {"age": 22,"weight": 42},
// {"age": 66,"weight": 56}
// ]
第十八式:这段代码为什么会报错,说好的分号可以省略呢?
console.log(123)
[12,2].filter(item => item > 3)
// Uncaught TypeError: Cannot read property '2' of undefined
// at :2:1
- 分号推断:编译原理里的分号推断,作用是在编程的时候,让程序员省略掉不必要的分号;
- JavaScript有着自动分号插入的机制(Automatic Semicolon Insertion),简称ASI(ASI 只是表示编译器正确理解了程序员的意图,并没有真的插入分号);
- 浏览器引擎的 Parser(负责将JS 源码转换为 AST)总是优先将换行符前后的符号流当作一条语句解析(带换行的多行注释与换行符是等效的);
所以在 Parser 眼里,以上代码是这样的:
console.log(123)[12,2].filter(item => item > 3)
,console.log(123)
没有返回值,既undefined
;[12,2]
中的方括号被视为读取console.log(123)
返回值中的属性2
,类似于根据下标取数组中的元素;- 为什么是取属性
2
呢,因为12,2
是个逗号表达式,表达式的值是最右边的“2”,如此以来,上面的报错信息就很好理解了。
不能省略的分号:
- for 循环头部的分号
- 作为空语句存在的分号
- 以
[、(、+、-、和/
五个字符开头的语句之前的分号
资料参考: 备胎的自我修养——趣谈 JavaScript 中的 ASI (Automatic Semicolon Insertion)
第十九式:西施宜笑复宜颦,丑女效之徒累身 —— window.btoa
、window.atob
,命名这么随意的API可以用来干什么?
单从命名来看,完全让人摸不着头脑的两个API,我们到底可以用他们来干些什么呢?(我甚至怀疑,如果在项目中使用这样的命名,完全可能被同事打,哈哈)
window.atob()
函数用来解码一个已经被base-64编码过的数据。你可以使用window.btoa()
方法来编码一个可能在传输过程中出现问题的数据,并且在接受数据之后,使用window.atob()
方法来将数据解码。例如:你可以把ASCII里面数值0到31的控制字符进行编码,传输和解码。window.btoa()
:将ASCII字符串或二进制数据转换成一个base64编码过的字符串,该方法不能直接作用于Unicode字符串。- 在各浏览器中,使用
window.btoa
对Unicode字符串进行编码都会触发一个字符越界的异常。 - 前端可以使用这两个API对URL路由参数、敏感信息等进行转码,防止明文暴露。
let encodedData = window.btoa("Hello, world"); // 编码
console.log(encodedData); // SGVsbG8sIHdvcmxk
let decodedData = window.atob(encodedData); // 解码
console.log(decodedData); // Hello, world
let encodeUTF = window.btoa(encodeURIComponent('啊'));
console.log(encodeUTF); // JUU1JTk1JThB
let decodedUTF = decodeURIComponent(atob(encodeUTF));
console.log(decodedUTF); // 啊
资料参考: window.atob()与window.btoa()方法实现编码与解码 | WindowOrWorkerGlobalScope.atob() | WindowOrWorkerGlobalScope.btoa()
第二十式:escape
、encodeURI
、 encodeURIComponent
,这些编码 API 有什么区别?
escape
是对字符串(string)进行编码(而另外两种是对 URL),作用是让它们在所有电脑上可读。编码之后的效果是%XX
或者%uXXXX
这种形式。其中 ASCII 字母、数字、@*/+
,这几个字符不会被编码,其余的都会。最关键的是,当你需要对 URL 编码时,请忘记这个方法,这个方法是针对字符串使用的,不适用于 URL;encodeURI
和encodeURIComponent
都是编码 URL,唯一区别就是编码的字符范围;encodeURI
方法不会对下列字符编码:ASCII 字母、数字、~!@#$&*()=:/,;?+'
;encodeURIComponent
方法不会对下列字符编码:ASCII 字母、数字、~!*()'
;- 也就是
encodeURIComponent
编码的范围更广,会将http://XXX
中的//
也编码,会导致 URL 不可用。(其实 java 中的URLEncoder.encode(str,char)
也类似于这个方法,会导致 URL 不可用)。 使用场景:
- 如果只是编码字符串,不和 URL 有半毛钱关系,那么用
escape
,而且这个方法一般不会用到; - 如果你需要编码整个 URL,然后需要使用这个 URL,那么用
encodeURI
; - 当你需要编码 URL 中的参数的时候,那么
encodeURIComponent
是最好方法; - 某些场景下,编码之后导致URL不可用(比如笔者曾遇到预览附件时某些附件URL无法打开的问题),可尝试考虑是否是因为特殊字符导致的。
- 如果只是编码字符串,不和 URL 有半毛钱关系,那么用
- 如果不生效可以用两次编码:关于两次编码的原因
资料参考: escape、encodeURI 和 encodeURIComponent 的区别
第二十一式:这不是我访问的页面 —— 经常碰到移动端DNS域名劫持问题?来一起了解下HTTPDNS是什么,解决了什么问题吧
对于互联网,域名是访问的第一跳,而这一跳很多时候会“失足”(尤其是移动端网络),导致访问错误内容、失败连接等,让用户在互联网上畅游的爽快瞬间消失。但凡使用域名来给用户提供服务的互联网企业,都或多或少地无法避免在有中国特色的互联网环境中遭遇到各种域名被缓存、用户跨网访问缓慢等问题。
- DNS 解析过程:
- 什么HttpDNS:
HTTPDNS 利用 HTTP 协议与 DNS 服务器交互,代替了传统的基于 UDP 协议的 DNS 交互,绕开了运营商的 Local DNS,有效防止了域名劫持,提高域名解析效率。另外,由于 DNS 服务器端获取的是真实客户端 IP 而非 Local DNS 的 IP,能够精确定位客户端地理位置、运营商信息,从而有效改进调度精确性。
HttpDNS 主要解决的问题:
- Local DNS 劫持:由于 HttpDns 是通过 IP 直接请求 HTTP 获取服务器 A 记录地址,不存在向本地运营商询问 domain 解析过程,所以从根本避免了劫持问题。
- 平均访问延迟下降:由于是 IP 直接访问省掉了一次 domain 解析过程,通过智能算法排序后找到最快节点进行访问。
- 用户连接失败率下降:通过算法降低以往失败率过高的服务器排序,通过时间近期访问过的数据提高服务器排序,通过历史访问成功记录提高服务器排序。
HttpDNS的原理
- 客户端直接访问HttpDNS接口,获取业务在域名配置管理系统上配置的访问延迟最优的IP。(基于容灾考虑,还是保留次选使用运营商LocalDNS解析域名的方式);
- 客户端获取到IP后就直接向此IP发送业务协议请求。以Http请求为例,通过在header中指定host字段,向HttpDNS返回的IP发送标准的Http请求即可。
详细资料参考: 全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等
第二十二式:depcheck
一下你的前端项目中是否存在未使用的依赖包
很多时候,也许我们的前端项目是基于某个已有的项目进行”复制搭建“,或者直接使用UmiJS这样的企业级 react 应用框架,又或者基于Ant Design Pro等开源项目进行删改,难免会存在未使用到的依赖包被安装,拖累项目安装速度,增大项目打包体积等,这时,我们就可以考虑使用depcheck
找出那些未使用的依赖包进行移除。
npm install depcheck -g
cd 到要检查的项目目录,运行 depcheck
D:project>depcheck Unused devDependencies #未使用的依赖 * @antv/data-set * echarts * echarts-for-react * qs * Unused devDependencies #未使用的devDependencies * chalk * enzyme * express Missing dependencies #缺少的dependencies * immutability-helper: .srccomponentsEditColumsEditColumnsTable.js * slash2: .configconfig.js
UmiJS学习参考: UmiJS | [[react]初识Umi.JS]( https://www.jianshu.com/p/dc4...
第二十三式:防止误操作,如何在组件卸载、路由跳转、页面关闭(刷新)之前进行提示
工作中经常会有大表单填写、提交这样的需求,如果用户写了大量内容,因为误操作,刷新或者关闭了页面,填写信息用没有做缓存,此时用户的内心应该是奔溃的。
React组件卸载、路由跳转、页面关闭(刷新)之前进行提示(如果是AntD Modal弹窗里面的表单,也可视情考虑将maskClosable
属性设置为false,防止误点蒙层导致弹窗关闭):
//监听窗口事件
useEffect(() => {
const listener = (ev) => {
ev.preventDefault();
ev.returnValue = '确定离开吗?';
};
window.addEventListener('beforeunload', listener);
return () => {
// 在末尾处返回一个函数
// React 在该函数组件卸载前调用该方法(实现 componentWillUnmount)
window.removeEventListener('beforeunload', listener);
};
}, []);
第二十四式:不带括号也能执行函数调用?console.loghello world
会打印出什么
- 直接看结果:
console.log`hello world` // 打印出一个数组:["hello world", raw: Array(1)]
- 再看看以下代码:
const name = 'jack'
const gender = false
// 带括号
console.log(`hey, ${name} is a ${gender ? 'girl' : 'boy'}.`) // hey, jack is a boy.
// 不带括号
console.log`hey, ${name} is a ${gender ? 'girl' : 'boy'}.` // ["hey, ", " is a ", ".", raw: Array(3)] 'jack' 'boy'
从最后一行打印可以看出数组中的项是以'插入表达式'作为分割生成的,并且插入表答式中的内容参数,也会依次打印出来。这就是带标签的模板字符串。
- 模板字符串的语法:
// 普通
`string text`
// 换行
`string text line 1
string text line 2`
// 插值
`string text ${expression} string text`
// 带标签的模板字符串
tag `string text ${expression} string text`
- 可以做什么:
const name = 'jack'
const gender = false
function myTagFunc(strings, name, gender) {
const sex = gender ? 'girl' : 'boy'
// return 'hello world'
return strings[0] + name + strings[1] + sex + strings[2]
}
// result 的值是myTagFunc函数的返回值
// 如果myTagFunc返回 hello world,result就是hello world
// 这样可在一定程度上避免在模板字符串内写复杂的逻辑
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result) // hey, jack is a boy.
在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串,而不经过特殊字符的替换。
function tag(strings) {
console.log(strings.raw[0]);
}
tag`string text line 1 n string text line 2`;// "string text line 1 n string text line 2"
console.log`string text line 1 n string text line 2` // ["string text line 1 ↵ string text line 2", raw: Array(1)]
参考资料: MDN-带标签的模板字符串 | 带标签的模板字符串
第二十五式:还在用闭包实现自增Id?何不试试优雅大气的Generator?
- 闭包
function createIdMaker(){
let id = 1;
return function (){
return id ++;
}
}
const idMaker = createIdMaker();
console.log(idMaker()) // 1
console.log(idMaker()) // 2
console.log(idMaker()) // 3
- Generator
function * createIdMaker() {
let id = 1
while(true) yield id ++;
}
const idMaker = createIdMaker()
console.log(idMaker.next().value) // 1
console.log(idMaker.next().value) // 2
console.log(idMaker.next().value) // 3
第二十六式:年轻人不讲武德,谁动了我的对象 —— 对象属性会自己偷偷排队?
程序员眼里只有女神,对象是不会有的,就算有,还能无可挑剔、百依百顺不成?缺对象那就new一个咯,个性化定制,绝对的理想型。控制不了现实里的对象还能控制不了new出来的对象了?事实上,你,真的不能。
- 试想一下,下面的代码会按照什么顺序输出:
function Foo() {
this[200] = 'test-200';
this[1] = 'test-1';
this[100] = 'test-100';
this['B'] = 'bar-B';
this[50] = 'test-50';
this[9] = 'test-9';
this[8] = 'test-8';
this[3] = 'test-3';
this[5] = 'test-5';
this['D'] = 'bar-D';
this['C'] = 'bar-C';
}
var bar = new Foo();
for (key in bar) {
console.log(`index:${key} value:${bar[key]}`);
}
在 ECMAScript 规范中定义了数字属性应该按照索引值大小升序排列,字符串属性根据创建时的顺序升序排列。我们把对象中的数字属性称为排序属性,在 Chrome V8 引擎 中被称为 elements,字符串属性就被称为常规属性,在 V8 中被称为 properties。在 V8 内部,为了有效地提升存储和访问这两种属性的性能,分别使用了两个线性数据结构来分别保存排序属性和常规属性。同时 v8 将部分常规属性直接存储到对象本身,我们把这称为对象内属性 (in-object properties),不过对象内属性的数量是固定的,默认是 10 个。更多详情可参考笔者之前的一篇文章浏览器是如何工作的:Chrome V8让你更懂JavaScript —— 【V8 内部是如何存储对象的:快属性和慢属性】一节。
- 结果揭晓
//输出:
// index:1 value:test-1
// index:3 value:test-3
// index:5 value:test-5
// index:8 value:test-8
// index:9 value:test-9
// index:50 value:test-50
// index:100 value:test-100
// index:200 value:test-200
// index:B value:bar-B
// index:D value:bar-D
// index:C value:bar-C
资料参考: 浏览器是如何工作的:Chrome V8让你更懂JavaScript
第二十七式:VS Code里竟然有谷歌开发者工具面板?它 和 Chrome有什么关系?
如下图所示,我们经常用的开发工具VSCode竟与浏览器如此相像,莫非他们是失散多年的兄弟?诶,你还别说,还真有那么点意思。(点击帮助【Help】 下的 切换开发人员工具即可打开以下面板)
VS Code 是基于 Electron (原来叫 Atom Shell) 进行开发的。Electron 基于 Node.js(作为后端运行时)和 Chromium(作为前端渲染),使得开发者可以使用 HTML, CSS 和 JavaScript 等前端技术来开发跨平台桌面 GUI 应用程序。Atom, GitHub Desktop, Slack, Microsoft Teams, WordPress Desktop 等知名软件都是基于 Electron 开发的。Electron比你想象的更简单,如果你可以建一个网站,你就可以建一个桌面应用程序。
VS Code 的其他的主要组件有:
- 壳:Monaco Editor
- 内核:Language Server Protocol(一个代码编辑器)
- Debug Adapter Protocol
- Xterm.js
参考资料: vs code的界面是用的什么技术? | Electron | Electron 快速入门
第二十八式:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate) —— 一个正经又有点邪气的组件封装
开始看到这行评级rate组件的代码,是在一篇充满邪气的文章信条|手撕吊打面试官系列面试题里,总觉得这个组件与那篇文章的文风不对应,甚至觉得这个实现还足够机智,值得借鉴,我是不是没救了,哈哈。
{
let rate = 3;
"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
}
参考资料: 信条|手撕吊打面试官系列面试题
第二十九式:Uncaught TypeError: obj is not iterable
,for of
遍历普通对象报错,如何快速使普通对象可被 for of
遍历?
for of
可以迭代Arrays(数组), Maps(映射), Sets(集合)、NodeList对象、Generator等,甚至连Strings(字符串)都可以迭代,却不能遍历普通对象?
// 字符串
const iterable = 'ES6';
for (const value of iterable) {
console.log(value);
}
// Output:
// "E"
// "S"
// "6"
// 普通对象
const obj = {
foo: 'value1',
bar: 'value2'
}
for(const item of obj){
console.log(item)
}
// Uncaught TypeError: obj is not iterable
我们先从对象的几个方法Object.values()
、Object.keys()
、Object.entries()
看起吧:
const obj = {
foo: 'value1',
bar: 'value2'
}
// 打印由value组成的数组
console.log(Object.values(obj))
// 打印由key组成的数组
console.log(Object.keys(obj))
// 打印由[key, value]组成的二维数组
console.log(Object.entries(obj))
// 方法一:使用of遍历普通对象的方法
for(const [, value] of Object.entries(obj)){
console.log(value)
}
// 普通对象转Map
console.log(new Map(Object.entries(obj)))
// 方法二:遍历普通对象生成的Map
for(const [, value] of new Map(Object.entries(obj))){
console.log(value)
}
普通对象为何不可被for of
迭代请参考下一式。
第三十式:可以遍历绝大部分数据类型的for of
为什么不能遍历普通对象?
- 普通对象为何不可被
for of
迭代
{
// 数组
const iterable = ['a', 'b'];
for (const value of iterable) {
console.log(value);
}
// Output:
// a
// b
}
{
// Set(集合)
const iterable = new Set([1, 2, 2, 1]);
for (const value of iterable) {
console.log(value);
}
// Output:
// 1
// 2
}
{
// Arguments Object(参数对象)
function args() {
for (const arg of arguments) {
console.log(arg);
}
}
args('a', 'b');
// Output:
// a
// b
}
可以看到,这些可被for of
迭代的对象,都实现了一个Symbol(Symbol.iterator)
方法,而普通对象没有这个方法。
简单来说,for of
语句创建一个循环来迭代可迭代的对象,可迭代的对象内部实现了Symbol.iterator
方法,而普通对象没有实现这一方法,所以普通对象是不可迭代的。
- 如何实现
Symbol.iterator
方法,使普通对象可被for of
迭代
// 普通对象
const obj = {
foo: 'value1',
bar: 'value2',
[Symbol.iterator]() {
// 不用担心[Symbol.iterator]属性会被Object.keys()获取到,
// Symbol.iterator需要通过Object.getOwnPropertySymbols(obj)获取,
// Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
const keys = Object.keys(obj);
let index = 0;
return {
next: () => {
if (index < keys.length) {
return {
value: this[keys[index++]],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
}
for (const value of obj) {
console.log(value); // value1 value2
}
上面给obj实现了Symbol.iterator
接口后,我们甚至还可以像下面这样把对象转换成数组:
console.log([...obj]); // ["value1", "value2"]
console.log([...{}]); // console.log is not iterable (cannot read property Symbol(Symbol.iterator))
更多关于for...of
的探讨,可参考笔者的一文带你理解:可以迭代大部分数据类型的 for…of 为什么不能遍历普通对象?。
参考资料: MDN:for...of | Understanding the JavaScript For...of Loop( 【译文】)| Iterator 和 for...of 循环
第三十一式:position定位只知道absolute
、fixed
、relative
和 static
?,sticky
其实可以很惊艳
- absolute:生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
- fixed:生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
- relative:生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
- static:默认值。没有定位,元素出现在正常的流中。
- sticky:粘性定位,该定位基于用户滚动的位置。当元素在屏幕内,它的行为就像
position:relative;
, 而当页面滚动超出目标区域时,它的表现就像position:fixed;
,它会固定在目标位置。
position:sticky
实现的惊艳吸顶效果可点击这里。
// 用法:nav元素实现粘性定位
nav {
position: -webkit-sticky;
position: sticky;
top: 0;
}
使用position:sticky
的过程中,也许会有一些坑,比如要想sticky生效,top属性或则left属性(看滚动方向)是必须要有明确的计算值的,否则fixed的表现不会出现。详情可参考《CSS世界》作者张鑫旭大佬的杀了个回马枪,还是说说position:sticky吧。
参考资料: CSS position 属性 | 杀了个回马枪,还是说说position:sticky吧
第三十二式:傍能行仁义,莫若妾自知 —— getBoundingClientRect
让你找准定位不迷失自我
- 什么是
getBoundingClientRect
Element.getBoundingClientRect()
方法,用来描述一个元素的具体位置,该位置的四个属性都是相对于视口左上角的位置而言的。对某一节点执行该方法,它的返回值是一个 DOMRect 类型的对象。这个对象表示一个矩形盒子,它含有:left、top、right 和 bottom 等只读属性,具体含义如下图所示:
offset 和 getBoundingClientRect() 区别
- offset 指偏移,包括这个元素在文档中占用的所有显示宽度,包括滚动条、padding、border,不包括overflow隐藏的部分;
- offset 的方向值需要考虑到父级,如果父级是定位元素,那么子元素的offset值相对于父元素;如果父元素不是定位元素,那么子元素的offset值相对于可视区窗口;
offsetParent
:获取当前元素的定位父元素:- 如果当前元素的父元素,有CSS定位(position为absolute、relative、fixed),那么
offsetParent
获取的是最近的那个父元素。 - 如果当前元素的父元素,没有CSS定位(position为absolute、relative、fixed),那么
offsetParent
获取的是body。
- 如果当前元素的父元素,有CSS定位(position为absolute、relative、fixed),那么
- getBoundingClientRect() 的值只相对于可视区窗口,所以在很多场景下更容易“找准定位”。
- 能做什么:滚动吸顶效果
笔者写此节之前有做过一个表格分页器固定在浏览器底部、表头滚动吸顶的效果,主要参考了position:sticky
属性和getBoundingClientRect
。写此节查阅资料时有发现【前端词典】5 种滚动吸顶实现方式的比较(性能升级版) 这篇文章,对五种吸顶方式做了详尽的分析和对比,大家有兴趣可以看看。同时,《CSS世界》作者张鑫旭大佬在杀了个回马枪,还是说说position:sticky吧对sticky
定位也有详尽的介绍。本来还想在后续的章节谈谈吸顶,现在可能需要重新评估了,哈哈。
滚动吸顶表格示例:
【前端词典】5 种滚动吸顶实现方式的比较(性能升级版)一文中的getBoundingClientRect
吸顶实现:
// html
// some code
// vue
export default {
data(){
return{
titleFixed: false
}
},
activated(){
this.titleFixed = false;
window.addEventListener('scroll', this.handleScroll);
},
methods: {
//滚动监听,头部固定
handleScroll: function () {
let offsetTop = this.$refs.pride_tab_fixed.getBoundingClientRect().top;
this.titleFixed = offsetTop < 0;
// some code
}
}
}
参考资料: getBoundingClientRect 方法 | 杀了个回马枪,还是说说position:sticky吧 | 【前端词典】5 种滚动吸顶实现方式的比较【性能升级版】 | JS 中的offset、scroll、client总结
第三十三式:Console Importer
让你的浏览器控制台成为更强大的实验场
平时开发中,我们经常会在控制台尝试一些操作,Console Importer是一个可以在Chrome Console面板安装(引入)loadsh、moment、jQuery、React等资源的插件,语法也很简单,比如$i('moment')
即可引入moment库,然后即可在控制台直接验证、使用这些库:
- 使用示例:
- 效果图:
- 引入资源方法:
$i('jquery') // 直接引入
$i('jquery@2') // 指定版本
$i('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js') // cdn地址
$i('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') // 引入CSS
链接: Console Importer | Chrome Web Store 地址
第三十四式:误用git reset --hard
,我真的没救了吗? —— 认识一下 git reflog
时光穿梭机
- 我们直奔主题,先看下面的问题:
懵懂的小白花费一周时间做了git log如下所示的6个功能,每个功能对应一个commit的提交,分别是feature-1 到 feature-6”:
然后错误的执行了强制回滚,git reset --hard 2216d4e
,回滚到了feature-1上,并且回滚的时候加了--hard,导致之前feature-2 到 feature-6的所有代码全部弄丢了,现在git log上显示如下:
然后,又在此基础上新添加了一个commit提交,信息叫feature-7:
请问:如何把丢失的代码feature-2 到 feature-6全部恢复回来,并且feature-7的代码也要保留
接下来,我们先回忆几个git命令:
git reset --hard
撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交,谨慎使用 –hard 参数,它会删除回退点之前的所有信息;git log
命令可以显示所有提交过的版本信息;git reflog
可以查看所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作);git cherry-pick
命令的作用,就是将指定的提交(commit)应用于其他分支。
最后,给出解答:
git reflog
和git cherry-pick
- 首先,使用
git reflog
查看所以git操作记录,记下feature-7和feature-6的hash码。
- 其次,
git reset --hard cd52afc
回滚到feature-6。此时我们已经完成了要求的一半:成功回到了feature-6上,但是feature-7没了。 - 最后,
git cherry-pick 4c97ff3
,执行完成之后,feature-7的代码就回来了,大功告成。
- 首先,使用
更多git知识点推荐阅读GitHub联合创始人Scott Chacon 和 Ben Straub的开源巨作 《Pro Git》
第三十五式:文件上传只会使用 form 和 Ant Design Upload组件?
最近有做一个由其他部门提供接口的需求,上传文件的接口文档如下图所示,文件内容是base64格式,且要和其他参数一起传递。笔者以前做的需求,上传文件一般是通过form、Ant Design Upload组件、FormData等方式,上传成功得到一个URL,表单提交时将得到的URL传给后端;下载通过Blob、后端返回URL、发送邮件、或者前端生成Excel等方式。这次的上传使用了FileReader,简单记录相关实现。关于大文件的上传和下载,之后的章节会进行探讨。
- FileReader上传代码实现
// DOM
this.uploadFile(e)} />
// js
uploadFile(e) {
const file = e.target.files[0];
const reader = new FileReader();
// 处理loadend事件。该事件在读取操作结束时(要么成功,要么失败)触发。
reader.onloadend = () => {
this.setState({
// 存储
XXXFile: {
// 除了name外,file中可被读取的属性还包括size、type、lastModifiedDate
Name: file.name,
// base64格式文件数据
// 一次性发送大量的base64数据会导致浏览器卡顿,服务器端接收这样的数据可能也会出现问题。
Buffer: reader.result.replace(/data.*base64[,]/, '')
}
})
}
reader.readAsDataURL(file);
}
FileReader方法拓展:
FileReader.abort()
:中止读取操作。在返回时,readyState属性为DONE。FileReader.readAsArrayBuffer()
:开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.FileReader.readAsBinaryString()
:开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。FileReader.readAsDataURL()
:开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL
格式的Base64字符串以表示所读取文件的内容。FileReader.readAsText()
:开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
其他文件上传参考资料:
第三十六式:2**53 === 2 ** 53 + 1
?如果没有BigInt,该如何进行大数求和?
Number.MAX_SAFE_INTEGER
:值为9007199254740991
,即2 ** 53 - 1
,小于该值能精确表示。然后我们会发现2**53 === 2 ** 53 + 1
为true
。Number.MAX_VALUE
::值为1.7976931348623157e+308
,大于该值得到的是Infinity
,介于Infinity
和安全值之间的无法精确表示。
既然我们不能实现直接相加,我们可以利用字符串分割成字符串数组的方式来对每一位进行相加。
- 大数相加实现
function add (str1, str2) {
// 转为单字符串数组
str1=(str1+'').split('');
str2=(str2+'').split('');
let result='';//存储结果
let flag=0; // 存储进位
while(str1.length || str2.length || flag){// 是否还有没有相加的位或者大于0的进位
// ~~str1.pop()得到最右边一位,并转成数字(~为按位取反运算符,详见第十四式)
// 对应位数字相加,再加上进位
flag += ~~str1.pop() + ~~str2.pop();
// 去除进位,然后进行字符串拼接
result = flag%10 + result;
// 进位,0或1
flag = +(flag>9);
}
// 去除开头(高位)的0
return result.replace(/^0+/, '');
};
// 2 ** 53:9007199254740992
// add(2**53, 1): "9007199254740993"
// 2**53+1: 9007199254740992
// BigInt结果
// 2n**53n+1n:9007199254740993n
- 加减乘除:
关于加减乘除的实现可参考大数运算js实现,基本思路:
1、大数加法和减法是一个道理,既然我们不能实现直接相加减,我们可以利用字符串分割成字符串数组的方式。
2、乘法:每个位数两两相乘,最后错位相加。
参考资料: JS 大数相加 | 前端应该知道的JavaScript浮点数和大数的原理