写在前面:
今年从春招到秋招面试了国内所谓的BAT、TMD等互联网企业前端开发。各大公司的面试风格可谓各有千秋,从编程基础、框架原理到算法、网络再到对新技术(比如PWA)的认知地考核,其中尤其以前端基础JS编程为考核重点,各大公司对此地看重程度已经到了一言不合就"丢算法撸代码"的地步。而小编在笔试机试这样的编码过程中居然一时忘掉了部分基本操作,结果不言而喻,就好比打野忘了带惩戒,出门忘了买装备,闪现送人头啊。痛定思痛,下定决心将这些基本操作好好整理一番日常背记,也当是做一个前端技能的分享和讨论了,希望广大前端爱好者能够在评论区查漏补缺。(结尾有惊喜哟)
1.基本类型和引用类型
基本类型:Number, String, Null, Undefined, Boolean, Symbol(ES6数据类型)
引用类型:Object、Array、RegExp、Date、Function、单体内置对象(Global、Math)
区别:基本类型,操作和保存在变量的实际的值(保存在栈区);引用类型,值指向内存空间的引用,就是地址,所指向的内存中保存着变量所表示的一个值或一组值,所以操作的是对象的引用(引用存放在栈区,实际对象保存在堆区)。
1.1 类型检测
使用 typeof
进行基本类型检测,使用instanceof
检测对象还是数组
类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
String | "string" |
Symbol(ECMAScript 6 新增) | "symbol" |
函数对象 | "function" |
任何其他对象 | "object" |
检测数组和对象:(Array其实是Object 的子类)
var a = [];
console.log(a instanceof Array); // true
console.log(a instanceof Object); // true
var a = {};
console.log(a instanceof Array); // false
console.log(a instanceof Object); // true
1.2 String操作
String <=> Number:
字符串转数字:
parseInt(str, 进制) // 默认十进制, 转化为整数,小数点后默认不保留
parseFloat(str) // 转化为浮点数
+str // 屌屌的转化方法, 但字符串包含非数字会报错
数字转字符串:
let str1 = num.toString();
let str2 = String(num);
let str3 = num + '';
String <=> Array:
字符串转数组
let arr = str.split(',') // 一般是空格或者逗号
数组转字符串
let str = arr.join('');
模板字符串
`Hello, ${变量}`
1.3 常用正则
去逗号
let newStr = str.replace(/,/g,'');
去空格(一般输入框输入都要做这个)
let newStr = str.replace(/(^\s*)|(\s*$)/g, ''); // 去除左右空格
let newStr = str.replace(/\s+/g,""); // 去除所有空格
用户名
let userPattern = /^[a-zA-Z0-9_-]{4,16}$/; // 4到16位(字母,数字,下划线,减号)
userPattern.test(str);
电话号码
let mPattern = /^1[34578]\d{9}$/;
附:密码/身份证号/E-mail/URL就不上了,太长了背了没意义,用的时候查就好了
1.4 Array操作
在阿里腾讯头条的笔试机试中,对数组的操作可以说是前端算法的核心,数组操作的基本方法全是必背的重中之重。
1.4.1 Array.from()
-
将伪数组对象和可迭代对象[见附录]转化为数组:
let arr = Array.from(str/new Set()/new map());
-
转化数组后对每项进行操作
let arr = Array.from('123', item => parseInt(item) + 1); // 2, 3, 4
-
去重(可以说这是最精彩的地方了)
Array.from(new Set(arr));
1.4.2 拷贝数组
使用以下方法能复制数组保存在堆内存中的值,但不能深拷贝数组(数组中的数组或者对象依旧只是复制了引用没有复制到其在堆内存中的值)。[数组和对象的深浅拷贝以及for循环递归实现方式本文不涉及]
let arr2 = [...arr1];
let arr2 = arr1.slice();
let arr2 = arr1.concat([]); // 此方法不常用
深拷贝(对象也是如此)
let arr2 = JSON.parse(JSON.stringify(arr1));
1.4.3 找出最大最小值
Math.max.apply(null, arr);
Math.min.apply(null, arr);
// ES6的写法
Math.max(...arr);
1.4.4 数组排序
-
sort()
1.数组排序 function compare(a, b) { return a-b; } arr.sort(compare); 2.数组对象排序(开发中常用) function compare(property) { return (obj1, obj2) => { return obj1[property] - obj2[property]; } } let students = [{name: "xx", age: 20}, {name: "hh", age: 19}]; students.sort(compare('age'));
1.4.5 其他方法
push(item) // 末尾添加
pop() // 删除末尾
shift() // 删除开头
unshift() // 开头添加
sort() // 排序
reverse() // 反转
slice(start, end) // 截断拷贝, 接收起始和结束位置两参数并返回相应的数组,不影响原数组
splice(index, num) // 切片,取出从index位置开始的num个值,原数组被截取
splice(index, 0, sth) // 插入sth
splice(index, 1, sth) // 替换为sth
▲ forEach(item, index, array) // 对每一项执行某些操作
▲ filter(item, index, array) // 返回满足条件(true)的项组成的数组
▲ map(item, index, array) // 返回对每一项执行某些操作组成的数组
every(item, index, array) // 如果该函数对每一项都返回true,则返回true
some(item, index, array) // 如果该函数对某一项返回true,则返回true
reduce() // 从头到尾逐个遍历,迭代数组中的所有项
includes(sth, index); // 检测数组中是否含有sth,index代表开始查找的位置,返回布尔值
另:二分查找
1.4.5 对象数组根据对象某key值去重(日常工作常用)
let arr = [{showId: 'C', age: '11'}, {showId: 'A', age: '11' },
{ showId: 'B', age: '11'}, { showId: 'B', age: '11'},
{ showId: 'B', age: '12'},{ showId: 'B', age: '13'}];
// 根据showId去重到新数组newArr
const newArr = [];
arr.forEach(item => {
// 过滤,如果有重复项就添加到过滤数组,那么长度就不为0就不会推入新数组。
// 如果没有重复项长度就为0就推入新数组。
newArr.filter(m => m.showId === item.showId).length === 0 &&
newArr.push(item);
});
1.5 Object操作
1.5.1 创建
创建方式:工厂模式、构造函数模式、原型模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式。
工厂模式
function createObj(key) {
const obj = new Object();
obj.key = key;
obj.sayKey = function(){ 方法 };
return obj;
}
// 使用:const xuqingfeng = createObj('xuqingfeng');
构造函数(对象属性最好用构造函数) + 原型(对象方法最好用原型),这样的话每个实例都会有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度地节省了内存:
// 构造函数模式用于定义实例属性
function Person(name) {
this.name = name;
}
// 原型模式用于定义方法和共享的属性
Person.prototype = {
/* 默认情况下,所有原型对象都会自动获得一个constructor属性,
这个属性是一个指向prototype属性所在函数的指针,
这里的Person.prototype.constructor指向Person */
constructor : Person,
sayName : function() { 方法 }
}
ES6的简写(简直不要太好用, 三大框架很多写法基于此):
属性简写:
let keyVal = 'xuqingfeng';
const obj = {keyVal}; // obj: { keyVal: 'xuqingfeng' }
方法简写:
const obj = {
method() {}
};
获取对象属性:
const { keyVal } = obj; // React的const { data } = this.props; 就是这么个原理,前提是对象中也有对应属性(key)
1.5.2 继承
继承方式:原型链、借用构造函数、组合继承、原型式继承、寄生式继承、寄生组合式继承。
组合继承
这种方式既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function A_Obj(name) {
this.name = name;
}
A_Obj.prototype.sayName = function() { A_Obj的方法 };
function B_Obj(name, age) {
A_Obj.call(this, name); // 继承属性
this.age = age;
}
B_Obj.prototype = new A_Obj(); // 继承A_Obj的所有方法
B_Obj.prototype.constructor = B_Obj; // 改变指向非常关键
B_Obj.prototype.sayAge = function() { B_Obj的方法 };
object.create()实现对象继承 - 特别地提及下这个方法,它可以直接使用创建一个新对象
// 实现继承 - 方法
let extend = (Child, Parent) => {
Child.prototype = Object.create(Parent.prototype); // 拷贝Parent原型对象
Child.prototype.constructor = Child; // 将Child构造函数赋值给Child的原型对象
}
// 实例
const Par = function () { this.name = 'xuqingfeng'; }
Par.prototype.getName = function () { return this.name; }
// 继承
const Cld = function () { Par.call(this); }
extend(Cld, Par);
// 使用
let testChild = new Cld();
console.log(testChild.getName())
1.5.3 拷贝
浅拷贝:
const copyObj = Object.assign({}, obj);
const Obj2 = {...Obj1};
浅拷贝并修改key的value或添加key与value: const Obj2 = {...Obj1, ['key']: 'newOrCover'}
,示例如下
const firObj = { a: '1', s: { ss: 'sss' } };
const secObj = { ...firObj, ['b']: '2' }
secObj.a = '777';
firObj.a = '666';
secObj.s.ss = 'secObj-s';
firObj.s.ss = 'firObj-s';
console.log(firObj, secObj); // { a: '666', s: { ss: 'firObj-s' } } { a: '777', s: { ss: 'firObj-s' }, b: '2' }
并集-合并两个对象/数组,后者覆盖前者(深度覆盖),最终形成并集: const Obj3 = Object.assign({}, Obj1, Obj2);
,示例如下
const firObj = { a: '1', b: 'b', s: { ss: 'fir-s' } };
const secObj = { a: '2', c: 'c', s: { ss: 'sec-s' } }
const newObj = Object.assign({}, firObj, secObj);
console.log(newObj); // { a: '2', b: 'b', s: { ss: 'sec-s' }, c: 'c' }
const firArr = [1, 2, 3, 'a', 'b'];
const secArr = [1, 3, 5, 7];
const newArr = Object.assign([], firArr, secArr);
console.log(newArr); // [ 1, 3, 5, 7, 'b' ]
深拷贝[ for循环递归深拷贝见上面数组,Object.assign
合并对象和深拷贝移步MDN ]:
let obj2 = JSON.parse(JSON.stringify(obj1));
1.5.4 其他方法
Object.freeze() // 冻结对象:其他代码不能删除或更改任何属性。
Object.keys() // 返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.values() // 返回给定对象自身可枚举值的数组。
Object.entries() // 返回给定对象自身可枚举属性的[key, value]数组
Object.defineProperty() // vue数据双向绑定的核心方法,建议上 MDN 一观
1.6 Function操作
1.6.1 参数转化为数组(不知参数个数)
数组原型slice方法
function fc() {
Array.prototype.slice.call(arguments) ; // 这个方法可以将只要具有length属性的对象转成数组
}
▲ rest 参数
function fc(...arr) { console.log(arr); }
参数cancat为数组
function fc() { return [].concat.apply([], arguments); }
1.6.2 检测函数参数是否含有某个值(sth)
[].includes.call(arguments, sth)
1.6.3 函数设置可改的默认参数
function func1(a, b='123123',c={id: 1}){
console.log(a,b,c)
}
func1('徐清风','xuqingfeng', 44) // 徐清风, xuqingfeng, 44
1.7 Math
与 Date
Math方法:
Math.ceil() // 执行向上舍入
Math.floor() // 执行向下舍入
Math.round() // 执行标准舍入
▲ Math.random() // 返回大于等于0小于1的随机数
获取当前时间:
let now = new Date();
console.log(now.toLocaleString()); // 2018-8-23 17:48:47
console.log([now.getFullYear(), now.getMonth()+1, now.getDate()].join('-')); // 2018-8-23
2.JS编程基本操作
2.1 三元运算 - 用起来超刺激
公式:条件1 ? 真结果1 : ( 条件1.1 ? 真结果1.1 : (条件1.1.1 ? 真结果1.1.1:假结果1.1.1))
, 一个常用示例:
node.style.display = (node.style.display === "block" ? "none" : "block");
2.2 在做JS判断时的 true
与false
(if判断、与非判断、三元运算判断等)
值为false
:false
、null
、undefined
、''(空字符串,空格不代表空)
、0
、NaN
值为true
:true
、对象
、字符串(包括空格)
、任意非0数值(包括Infinity)
2.3 与或非运算
&& :如果第一个值为 true,则 && 后面的值将显示在输出中,否则值为第一个值。
let a = false && 123; // false
let b = ' ' && 123; // 123, 注意这里是空格不是空
|| :如果第一个值为 false,则 || 后面的值将显示在输出中,否则值为第一个值。
let a = false || 123; // 123
let b = ' ' || 123; // ' '
附录:
- 伪数组对象和可迭代对象:
- 伪数组对象(拥有一个
length
属性和若干索引属性的任意对象) - 可迭代对象(可以获取对象中的元素,如
Map
和Set
等)
-
福利-今年春秋招阿里腾讯头条机试(远程视频直接撸代码)题:
1. 正则电话号码 2. 两个超过JS数值范围的数字相加 3. 一片英文文章重复出现次数最多的单词及其出现次数 4. 创建一个 Person 类,其包含公有属性 name 和私有属性 age 以及公有方法 setAge; 创建一个 Teacher 类,使其继承 Person ,并包含私有属性 studentCount 和私有方法 setStudentCount 5. 兼容的事件监听方法 6. 快排 7. 拖拽一个方块随机移动 8. 事件观察者 9. promise按序执行三个函数 10. 封装一个flat方法,将多维数组扁平化为一维数组 11. 实现一个简易的JQ选择器功能(要求能够获取标签、类、Id) 12. 实现简易的虚拟DOM(意味着用对象构造DOM),然后生成DOM结构, 只要求属性值(比如class、id等属性能够正常获取和调用), 不要求节点增删查改(这涉及到diff算法)。 13. 对象数组根据某个属性去重【这个上面有提到】
以上知识点是个人日常开发常用的,关于编程思想、算法逻辑这些大知识点这里就不一一列出了,毕竟吃透这些东西不是光靠背下来就能解决的,更多的是积累、理解和自己的思考,这些东西是程序员一辈子都学不完的,所以列出毫无意义。
因为写这些小知识点也只是一时兴起,肯定有很多遗漏,文首就有提及过写这些内容的目的就是分享和讨论,所以欢迎大家在评论区进行补充,如果有必要的话可以在github上开一个issue。
2018-11-21 更新:新增数组、对象浅拷贝方法,新增获取对象key方法,新增对象数组去重方法,面试新题
2019-2-27 更新:新增常用的对象拷贝方法和合并方法