JS 测试一
具体实现
我的解答
4;
arr.filter((item) => !!item)[
(1, NaN, empty, NAN)
];
var arr3 = [];
for (var i = 0; i < arr.length; i++) {
arr3.push(arr[i]);
}
for (var j = 0; j < arr2.length; j++) {
arr3.push(arr2[i]);
}
[...new Set(arr3)];
最佳解答
4
arr.filter((item) => true)
[1, NaN, empty, NAN]
arr.concat(arr2)
[...new Set(arr3)]
实现要点
- 空数组项也会作为 length 的一部分;空数组项和空字符串项是有区别的。
- 第 2 题,本题只是过滤空数组项,不包括 null, undefined 这类。
- 去除空数组项两个比较好的回答:
- 数组中的 empty 元素不会参与数组项遍历,故只需返回 true 即可过滤掉 empty 元素(而不会牵连 0、NaN、null、undefined、” 这些)arr.filter(it => true)。然后补充,但是走 for 循环,还是会遍历 empty 数组项。
- 或者 filter 方法参数直接就是一个 function 即可。例如:~arr.filter(Boolean)~(纠正:不能是 Boolean,false 会被过滤),arr.filter(Number), arr.filter(String)
- 上面并不是最好的方法。数组有个 API,天然去除空数组项,arr.flat()。flat()可以让数组扁平化的方法。
- 第 3 题标准答案应该是[1, NaN, NaN],map 里面 Function 支持参数(value, index, arr),参见 wingmeng 的释义。
- 第 4 题就是 concat,可以数组合并。我自己用“连接猫”记忆这个 API。可以分别连接子项,也可以直接连接数组。如果不考虑兼容,可以[…arr, …arr2]。其他参考方法:Array.prototype.push.apply(arr3, arr2),也可以[].push.apply(arr3, arr2),此时 arr3 是合并后的数组。
- 数组去重。使用 new Set(arr3),然后把 Set 对象转换成数组。转数组两个常用方法,一个是 Array.from,还有一个是
[...]
。
JS 测试二
具体实现
我的解答
'1';
'2';
'0.04';
'0.04';
最佳解答
'1';
'2';
'0.04';
'0.04';
var oldtoFixed = Number.prototype.toFixed;
Number.prototype.toFixed = function(digits) {
var length = (parseFloat(this) + '').replace(/^\d+\.?/, '').length;
var len = length > digits ? length : digits;
var number = Number(this) + Math.pow(10, -len - 1);
return oldtoFixed.call(number, digits);
};
实现要点
- toFixed 有两个问题,一是兼容性,二是四舍五入不符合正常的四舍五入认知。金钱计算的时候容易出问题,必须两位小数。
- 应该返回字符串;补全末尾的 0。
- 机智是实现:方式一:替换小数点保留精度后面一位 5 为 6,方式二:给小数点保留精度后面补一位小数。其中方式 2 是最简单的
JS 测试三
具体实现
我的解答
document.cookie;
document.cookie = 'userid=1';
document.cookie = 'userid=1;expire=' + new Date(Date.now() + 24 * 3600 * 1000).toGMTString();
function getCookie(name) {
var strCookie = document.cookie;
var arrCookie = strCookie.split('; ');
for (var i = 0; i < arrCookie.length; i++) {
var arr = arrCookie[i].split('=');
if (arr[0] === name) {
return arr[1];
}
}
return '';
}
getCookie('_csrfToken');
document.cookie = 'ywkey=1;expire=' + new Date(Date.now() - 24 * 3600 * 1000).toGMTString();
localStorage.setItem('userid', 1);
if (timestamp <= Date.now()) {
localStorage.removeItem('userid');
}
最佳解答
function rewriteLocalStorage() {
if (!window.__rewrite__localStorage) {
Object.assign(window, {
__rewrite__localStorage: true,
__localStorage__setItem: localStorage.setItem,
__localStorage__getItem: localStorage.getItem,
__localStorage__removeItem: localStorage.removeItem
});
if (!localStorage.__expires) {
localStorage.__expires = '{}';
}
localStorage.setItem = function(key, value, millisecond) {
if (millisecond) {
let __expires = JSON.parse(localStorage.__expires);
__expires[key] = +Date.now() + millisecond;
localStorage.__expires = JSON.stringify(__expires);
}
window.__localStorage__setItem.call(this, key, value);
};
localStorage.getItem = function(key) {
window.clearExpires();
return window.__localStorage__getItem.call(this, key);
};
localStorage.removeItem = function(key) {
let __expires = JSON.parse(localStorage.__expires);
delete __expires[key];
localStorage.__expires = JSON.stringify(__expires);
return window.__localStorage__removeItem.call(this, key);
};
window.clearExpires = function() {
let __expires = JSON.parse(localStorage.__expires);
for (let key in __expires) {
if (__expires[key] < Date.now()) {
localStorage.removeItem(key);
}
}
};
}
}
function destoryRewriteLocalStorage() {
if (window.__rewrite__localStorage) {
localStorage.setItem = window.__localStorage__setItem;
localStorage.getItem = window.__localStorage__getItem;
localStorage.removeItem = window.__localStorage__removeItem;
delete window.__rewrite__localStorage;
delete window.__localStorage__setItem;
delete window.__localStorage__getItem;
delete window.__localStorage__removeItem;
delete window.clearExpires;
localStorage.removeItem('__expires');
}
}
实现要点
- 通过前端手段设置 cookie 的过期时间,一定要使用服务器时间,不能使用本地时间。两个原因:一个是和服务端统一;本地时间是不准的,用户可以修改的;
- 获取 cookie 方法,一类:字符分隔,数组遍历,查询对应的键值。二类:正则,可以看看 Seasonley 的实现。
- ?<指的什么?--- (“(?<=\bsub)\w+\b”定位“sub”后面的字符串)。零宽断言。有兼容性问题,见下面讨论,不推荐实际项目使用。
- 设置过期时间可以 expires,也可以是 max-age。区别是什么呢?max-age 是更新的过期时间用法,是 IE9+浏览器才支持的,更容易理解和记忆。
- 删除 cookie 可以设置过期时间为之前。
- localStorage.setItem(‘userid’, 1)或者简写:localStorage.userid = 1;
- localStorage 过期时间,JSON.stringify 和 JSON.parse 是可读性很不错,也容易维护的实现。
- 可以以上 localStorage 重写,隐藏时间过期的细节,非常适合作为小工具,小组件。
JS 测试四
具体实现
我的解答
content.length <= 140;
content.trim().replace(/\s+/g, ' ');
Math.ceil([...content.trim().replace(/\s+/g, ' ')].map((item) => item.charCodeAt()).join('').length / 2);
最佳解答
function testLength(str) {
return str.length > 140;
}
function testLengthNoSpace(str) {
return testLength(str.trim().replace(/\s+/g, ' '));
}
function testLengthNoASCII(str) {
str = str.trim().replace(/\s+/g, ' ');
let ascLen = 0;
for (let i = 0; i < str.length; i++) {
str.charCodeAt(i) < 128 && ascLen++;
}
return str.length - Math.floor(ascLen / 2) > 140;
}
function testLengthNoURL(str) {
let placeHolder = Array(21).join(',');
str = str
.trim()
.replace(/\s+/g, ' ')
.replace(/http:\/\/[\x21-\x7e]{13,}?(?=[^\x21-\x7e]|$)/g, placeHolder)
.replace(/https:\/\/[\x21-\x7e]{12,}?(?=[^\x21-\x7e]|$)/g, placeHolder);
return testLengthNoASCII(str);
}
实现要点
- 空格替换直接 trim()方法,以及/\s+/g 正则即可;
- ASCII 字符非连续也算半个字符,可以使用
str.match(/[\x00-\xff]/g).length
;
- 网址优先判断;
- 替换的字符务必是非 ASCII 字符(否则会认为是 5 个字符长度);
- 20 个 ASCII 字符长度,可以 Array(20).join()或者’,‘.repeat(20);
JS 测试五
具体实现
最佳解答
var testSpecs = [
'在LeanCloud上,数据存储是围绕AVObject进行的。',
'今天出去买菜花了 5000元。',
'我家的光纤入户宽带有 10Gbps,SSD 一共有 10TB。显示器分辨率宽度是1920px。',
'今天是 233 ° 的高温。新 MacBook Pro 有 15 % 的 CPU 性能提升。',
'刚刚买了一部 iPhone ,好开心 !',
'她竟然对你说「喵」?!?!??!!喵??!!Meow...',
'你好,我是破折号——一个不苟言笑的符号。',
'核磁共振成像 (NMRI) 是什么原理都不知道? JFGI!',
'这件蛋糕只卖 1000 元。',
'乔布斯那句话是怎么说的?「Stay hungry,stay foolish。」',
'推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。'
];
charCheck(testSpecs.join(''));
function charCheck(str) {
var symbols = {
full: '!()【】『』「」《》“”‘’;:,。?、',
half: '!-_()[]{}<>"\';:,./?`',
getRegStr: function(key) {
var symbols =
typeof key === 'string'
? this[key]
: (function(that) {
if (key instanceof Array) {
return key.reduce(function(total, cur) {
return (total += that[cur]);
}, '');
}
return '';
})(this);
return symbols
.split('')
.map(function(s) {
return '\\' + s;
})
.join('|');
},
getRegRule: function(key, usedAs) {
var strs = this.getRegStr.call(this, 'full');
var regArr = ['(\\S+)', '([' + strs + '])'];
var temp = [].concat(regArr);
temp.reverse();
if (usedAs === 'rule') {
return new RegExp('(:?' + regArr.join('\\s+') + ')|' + '(:?' + temp.join('\\s+') + ')', 'g');
} else if (usedAs === 'format') {
return [new RegExp(regArr.join('\\s+'), 'g'), new RegExp(temp.join('\\s+'), 'g')];
}
}
};
var regExps = [
{
rule: /([\u4e00-\u9fa5]+[a-zA-Z]+)|([a-zA-Z]+[\u4e00-\u9fa5]+)/g,
format: [/([\u4e00-\u9fa5]+)([a-zA-Z]+)/g, /([a-zA-Z]+)([\u4e00-\u9fa5]+)/g],
matches: '$1 $2',
msg: '中英文之间需要增加空格'
},
{
rule: /([\u4e00-\u9fa5]+\d+)|(\d+[\u4e00-\u9fa5]+)/g,
format: [/([\u4e00-\u9fa5]+)(\d+)/g, /(\d+)([\u4e00-\u9fa5]+)/g],
matches: '$1 $2',
msg: '中文与数字之间需要增加空格'
},
{
rule: /(\d)([A-Z]+)/g,
matches: '$1 $2',
msg: '数字与大写英文单位之间需要增加空格'
},
{
rule: /(\d+)\s+(°|%)/g,
matches: '$1$2',
msg: '° 或 % 与数字之间不需要空格'
},
{
rule: symbols.getRegRule('full', 'rule'),
format: symbols.getRegRule('full', 'format'),
matches: '$1$2',
msg: '全角标点与其他字符之间不加空格'
},
{
rule: new RegExp('(' + symbols.getRegStr('full') + ')\\1+', 'g'),
matches: '$1',
msg: '不重复使用中文标点符号'
},
{
rule: /(\S)(——)(\S)/g,
matches: '$1 $2 $3',
msg: '破折号前后需要增加一个空格'
},
{
rule: new RegExp('\\s*(' + symbols.getRegStr('half') + ')\\s*', 'g'),
matches: function(s) {
s = s.replace(/(^\s*)|(\s*$)/g, '');
return String.fromCharCode(s.charCodeAt() + 65248);
},
msg: '使用全角中文标点'
},
{
rule: /[\uFF10-\uFF19]/g,
matches: function(s) {
return String.fromCharCode(s.charCodeAt() - 65248);
},
msg: '数字使用半角字符'
},
{
rule: /[《|「](:?\s*[a-zA-Z]+\s*(。|[\uff00-\uffff])*\s*[a-zA-Z]*)+[\.|」|》]/g,
matches: function(s) {
return s.replace(/。|[\uff00-\uffff]/g, function($1) {
var half = String.fromCharCode($1.charCodeAt() - 65248);
if (!!~['&', '-', '+'].indexOf(half)) {
half = ' ' + half + ' ';
} else if (!!~[':', ',', ';'].indexOf(half)) {
half += ' ';
} else if (half === 'ㄢ') {
half = '.';
}
return half;
});
},
msg: '遇到完整的英文整句、特殊名词,其內容使用半角标点'
}
];
var result = str;
regExps.forEach(function(reg, idx) {
var format = reg.format;
var matches = reg.matches;
var tip = str.match(reg.rule);
if (tip) {
console.group('%c' + reg.msg + ' (X)', 'color: red');
console.log(tip.join('\n'));
console.groupEnd();
if (!format) {
result = result.replace(reg.rule, matches);
} else if (format instanceof Array) {
format.forEach(function(fmtReg) {
result = result.replace(fmtReg, matches);
});
} else if (Object.prototype.toString.call(format) === '[object RegExp]') {
result = result.replace(format, matches);
}
}
});
if (result === str) {
console.log('%c当前文本符合规范 (√)', 'color: green');
return;
}
console.group('按规范格式化后的文本:');
console.log('%c' + result, 'color: green');
console.groupEnd();
}
JS 测试六
具体实现
我的解答
var bankCode = '6222081812002934027';
bankCode.replace(/(\d{4})(?=\d)/g, '$1 ');
var numberCode = '57023754';
numberCode.replace(/(\d)(?=(\d{3})+$)/g, '$1,');
function roundNumber(number, n = 2) {
return Math.round(number * Math.pow(10, n)) / Math.pow(10, n);
}
function formatterFilesize(size, n) {
var K = 1024;
var M = 1024 ** 2;
var G = 1024 ** 3;
if (size < M) {
return roundNumber(size / K, n) + 'K';
} else if (size < G) {
return roundNumber(size / M, n) + 'M';
}
return roundNumber(size / G, n) + 'G';
}
formatterFilesize(2837475);
最佳解答
var bankCode = '6222081812002934027';
bankCode.replace(/(\d{4})/g, '$1 ');
var numberCode = '5702375';
Number(numberCode).toLocaleString('en-US');
var filesize = 2837475;
function format(size) {
return (
(size > 1024 ** 3 && (size / 1024 ** 3).toFixed(2) + 'G') ||
(size > 1024 ** 2 && (size / 1024 ** 2).toFixed(2) + 'M') ||
(size / 1024).toFixed(2) + 'K'
);
}
format(2837475555);
format(2837475);
format(28374);
实现要点
- 这个匹配值得大家关注:bankCode.match(/\d{3,4}/g).join(’ ’)。然后“$&是最后匹配的字符”。
- 数字千位分隔符表示语义会更好。
<meta name="format-detection" content="telephone=no">
这个其实不推荐的。Number(numberCode).toLocaleString()是最佳实现了。toLocaleString 保留三位小数(细节可以关注下)。
- Intl.NumberFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
- 最后一题要点:注意取几位小数,最好向上取,然后注意下文件大小的单位是比特。
JS 测试七
具体实现
我的解答
var str =
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M2 2a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V2z" fill="#0067E6"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7 6a1 1 0 0 1 1-1h9l5 5v12a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6z" fill="#FEAEA5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M17 5l5 5h-4a1 1 0 0 1-1-1V5z" fill="#0067E6"/></svg>';
str.replace(/fill="(?!none")[^"]+"/gi, '');
btoa(str.replace(/fill="(?!none")[^"]+"/gi, ''));
encodeURIComponent(str.replace(/fill="(?!none")[^"]+"/gi, ''));
最佳解答
var str =
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M2 2a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V2z" fill="#0067E6"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7 6a1 1 0 0 1 1-1h9l5 5v12a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6z" fill="#FEAEA5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M17 5l5 5h-4a1 1 0 0 1-1-1V5z" fill="#0067E6"/></svg>';
var str1 = str.replace(/fill="(?!none")[^"]+"/gi, '');
btoa(unescape(encodeURIComponent(str1)));
str1.replace(/["%#{}<>]/g, encodeURI);
实现要点
- 老老实实用一个简单正则,然后 callback 中处理,虽然代码不是很简单,但是看得懂也不出错。简洁用法:
/fill="(?!none")[^"]+"/gi
。
- window.btoa(str)可以转 base64。但是如果有中文是会报错的。可以先 encodeURI 下,或者 encodeURIComponent 也可以。可以试试这个:btoa(unescape(encodeURIComponent(str)))。base64 到常规格式 window.atob(str);
- data:image/svg+xml;utf8, 加原始 SVG 代码是可以作为 CSS background 图片的,但是 Chrome 支持,IE 浏览器不支持。我们可以部分转义,”,%,#,{,},<,>。IE 浏览器也支持,包括 IE9。
str.replace(/[%#{}<>]/g, encodeURI)
。
JS 测试八
具体实现
我的解答
var phone = '13208033621 ';
phone.trim();
var phone = '13208033621';
function toSBC(str) {
var result = '';
var len = str.length;
for (var i = 0; i < len; i++) {
var cCode = str.charCodeAt(i);
cCode = cCode >= 0xff01 && cCode <= 0xff5e ? cCode - 65248 : cCode;
cCode = cCode === 0x03000 ? 0x0020 : cCode;
result += String.fromCharCode(cCode);
}
return result;
}
toSBC(phone);
var phone = '+8613208033621';
phone.replace(/^\+86/g, '');
var phone = '1320-8033-621';
var phone2 = '1320 8033 621';
var trimPhone = phone2.replace(/[-\s]/g, '');
if (trimPhone.length === 11) {
phone = trimPhone;
}
console.log(phone);
var phone = '13208033621';
var pattern = /1\d{10}/;
pattern.test(phone);
最佳解答
var phone = '13208033621 ';
phone.trim();
phone.replace(/^\s*|\s*$/g, '');
var phone = '13208033621';
function toSBC(str) {
var result = '';
var len = str.length;
for (var i = 0; i < len; i++) {
var cCode = str.charCodeAt(i);
cCode = cCode >= 0xff01 && cCode <= 0xff5e ? cCode - 65248 : cCode;
cCode = cCode === 0x03000 ? 0x0020 : cCode;
result += String.fromCharCode(cCode);
}
return result;
}
toSBC(phone);
[...'0123456789'].reduce((acc, cur, idx) => acc.replace(new RegExp(cur, 'g'), idx), phone);
var doubleByteNums = '0123456789';
phone.replace(/[0-9]/g, (matched) => doubleByteNums.indexOf(matched));
var dict = { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9 };
[...phone].map((item) => dict[item]).join('');
var phone = '+8613208033621';
phone.replace(/^\+86/g, '');
var phone = '1320-8033-621';
var phone2 = '1320 8033 621';
function formatSpace(strTel) {
return (strTel.match(/[0-9]/g).length === 11 && strTel.replace(/[-\s]/g, '')) || strTel;
}
var result = formatSpace(phone);
var result2 = formatSpace(phone2);
var phone = '13208033621';
var pattern = /^1\d{10}$/;
pattern.test(phone);
实现要点
- 去除前后空格:xxx.trim(),trim()不止过滤普通空格(Space 键敲出来的空格),IE9+,如果想要兼容 IE8,xxxx.replace(/^\s|\s$/g, "")。
- 全角转半角 String.fromCharCode(str.charCodeAt(i)-65248)是常规方法,适用于任何全角字符。本期可以只考虑数值的全角转半角,reduce()是一种方法(参考 Seasonley 的回答),还有 const doubleByteNums = ‘0123456789’;strTel.replace( new RegExp(’[0-9]’, ‘g’), matched => doubleByteNums.indexOf(matched))或者使用映射 dict = { “0”: 0,“1”: 1, “2”: 2,“3”: 3,“4”: 4, “5”: 5, “6”: 6, “7”: 7, “8”: 8, “9”: 9, }进行替换。注意:只有全角才需要 code 减值。
- /^+86/
- 需要数字个数匹配。因为输入框可能既支持输入手机号,又支持邮箱。
- /^1\d{10}$/
- 本小测的初衷:一个普通的手机账号输入框体验做好是不容易的事情。大部分的开发,都只是完成字符串前后空格的过滤就结束了,测试同学测试呢,也是可以通过的。啊,但实际上这并不是一个非常好的体验实现,正如我公众号文章( https://mp.weixin.qq.com/s/3iYaxKJLjLo_gWgb8lvabw )所讲的那样,做前端要想交互体验做到非常好,需要扎实的技术和经验积累作为前提的。例如输入框粘贴的时候自动在剪切板层把字符过滤,这个是需要技术积累的,需要对剪切板对象比较了解。 又例如全角半角的转化,以及+86 的过滤这都需要足够多的经验的。讲这个的目的是想让大家知道做前端开发要想做的东西非常好,关键是把技术给好好积累,充分扎实,这个是做小测的目的,也是你竞争力所在。