6-30日面试总结
今天在手机Boss上找了两家招聘前端实习生的岗位,约了下午面试。这是从学校出来第一次进行线下面试,还是有一点点紧张的
第一家公司:东信软件
公司的具体介绍我自己也不是很了解,就不过多介绍了,直接进行面试题的总结与分析
由于是第二天总结的,有一些面试题可能想不起来了,只是把一些我印象深刻的问题总结一下
自我介绍
我自己对于自我介绍准备的是不充分的,我只简短的说了一下自己的姓名,年龄,就读学校,以及说了一下自己对前端开发感兴趣之类的,非常简短,没有条理,这一点我需要强化一下。
💩 下面是我面试时候问到的问题,我的回答很烂很烂,就不过多赘述, 直接总结答案了
1.HTML标签的分类
闭合的角度
- 闭合标签
- 空标签
html中大部分都是闭合标签,只有少数为空标签
常见的空标签:
<input />、
<img />、
<area />、
<base />、
<link />等
位置特性
- 块级元素
- 行内元素
- 行内块级元素
块级元素(block)
特点:
- 可以设置宽高,内、外边距
- 独占一行
- 块级元素如果不设置宽度和高度,则【宽度默认为父级元素的宽度】、【高度根据内容大小自动填充】
常见的块级元素:
div、p、h1-h6、ol、ul、li、form、table
行级元素(inline)
特点:
- 不可设置宽高、上下、内外边距(左右内、外边距设置有效)
- 宽度和高度由【内容自动填充】
- 其它行级元素【共处一行】
常见的行级元素:
a(锚点)、b(加粗)、i(斜体)、span(常用内联容器,定义文本内区块)、lable(input元素定义标注(标记))
行内块元素(inline-block)
特点:
- 可以设置宽高、内外边距
- 可以与其他行内元素、内联元素【共处一行】
常见的内联元素:
input、img
是否具有语义
- 语义化标签
- 非语义化标签
常见的语义化标签:
<header></header> 头部
<nav></nav> 导航栏
<section></section> 区块(有语义化的div)
<main></main> 主要区域
<article></article> 主要内容
<aside></aside> 侧边栏
<footer></footer> 底部
2.选择器的优先级
选择器 | 格式 | 优先级权重 |
---|---|---|
id选择器 | #id | 100 |
类选择器 | .classname | 10 |
属性选择器 | a[ref=“eee”] | 10 |
伪类选择器 | li:last-child | 10 |
标签选择器 | div | 1 |
伪元素选择器 | li:after | 1 |
相邻兄弟选择器 | h1+p | 0 |
子元素选择器 | ul>li | 0 |
后代选择器 | li a | 0 |
通配符选择器 | * | 0 |
对于选择器的优先级:
- 标签选择器、伪元素选择器:1
- 类选择器、伪类选择器、属性选择器:10
- id 选择器:100
- 内联样式:1000
注意事项:
- !important声明的样式的优先级最高;
- 如果优先级相同,则最后出现的样式生效;
- 继承得到的样式的优先级最低;
- 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;
- 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
3.选择器的分类
- 通配符选择器,* 是通配符,表示其下的样式对所有元素生效,但通配符的优先级较低。
- id选择器,例如 #container
- 类选择器,例如 .box
- 标签选择器(类型选择器),例如 div span p 等元素标签,直接以标签名称作为样式选择器
- 后代选择器,有层级关系的叠加样式选择器,是样式选择器的一种组合使用,例如 div p
- 子选择器,例如 ul>li 或 div>p,对 ul 直接的子元素 li 设置样式,对 div 的直接子元素 p 设置样式。
- 伪类选择器,例如 a:hover,当鼠标悬浮于 a 标签时的样式。
- 伪元素选择器,例如常见的 ::before 和 ::after,单冒号写法也被现代浏览器支持(那是css2的语法)。
- 相邻兄弟选择器,例如 img + p,样式将对 img 图片后面紧跟着的 p 段落生效。
- 兄弟选择器,A~B 作用于 A 元素之后所有同层级 B 元素。
- 属性选择器,通过已经存在的属性名或属性值匹配元素,例如 a[href=“example.org”] 或 a[title]
4.说一下flex布局
Flex是FlexibleBox的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为Flex布局。行内元素也可以使用Flex布局。
注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。
采用Flex布局的元素,称为Flex容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称"项目"。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),项目默认沿水平主轴排列。
以下6个属性设置在容器上:
- flex-direction属性决定主轴的方向(即项目的排列方向)。
- flex-wrap属性定义,如果一条轴线排不下,如何换行。
- flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
- justify-content属性定义了项目在主轴上的对齐方式。
- align-items属性定义项目在交叉轴上如何对齐。
- align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
以下6个属性设置在项目上:
- order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
- flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
- flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
- flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
- align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
简单来说:
flex布局是CSS3新增的一种布局方式,可以通过将一个元素的display属性值设置为flex从而使它成为一个flex容器,它的所有子元素都会成为它的项目。
一个容器默认有两条轴:一个是水平的主轴,一个是与主轴垂直的交叉轴。
- 可以使用flex-direction来指定主轴的方向
- 可以使用justify-content来指定元素在主轴上的排列方式
- 可以使用align-items来指定元素在交叉轴上的排列方式
- 可以使用flex-wrap来规定当一行排列不下时的换行方式
对于容器中的项目,可以使用order属性来指定项目的排列顺序,还可以使用flex-grow来指定当排列空间有剩余的时候,项目的放大比例,还可以使用flex-shrink来指定当排列空间不足时,项目的缩小比例。
5.我要实现左边一个盒子固定宽度,右边自适应,我该如何实现(两栏布局实现)
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:
- 利用浮动,将左边元素宽度设置为200px,并且设置向左浮动。将右边元素的margin-left设置为200px,宽度设置为auto(默认为auto,撑满整个父元素)
.outer {
height: 100px;
}
.left {
float: left;
width: 200px;
background: tomato;
}
.right {
margin-left: 200px;
width: auto;
background: gold;
}
- 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置overflow: hidden; 这样右边就触发了BFC,BFC的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠。
.left{
width: 100px;
height: 200px;
background: red;
float: left;
}
.right{
height: 300px;
background: blue;
overflow: hidden;
}
- 利用flex布局,将左边元素设置为固定宽度200px,将右边的元素设置为flex:1。
.outer {
display: flex;
height: 100px;
}
.left {
width: 200px;
background: tomato;
}
.right {
flex: 1;
background: gold;
}
- 利用绝对定位,将父级元素设置为相对定位。左边元素设置为absolute定位,并且宽度设置为200px。将右边元素的margin-left的值设置为200px。
.outer {
position: relative;
height: 100px;
}
.left {
position: absolute;
width: 200px;
height: 100px;
background: tomato;
}
.right {
margin-left: 200px;
background: gold;
}
- 利用绝对定位,将父级元素设置为相对定位。左边元素宽度设置为200px,右边元素设置为绝对定位,左边定位为200px,其余方向定位为0。
.outer {
position: relative;
height: 100px;
}
.left {
width: 200px;
background: tomato;
}
.right {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 200px;
background: gold;
}
6.JS闭包
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
闭包就是:能够读取其他函数内部变量的函数
使用闭包主要为了设计私有的方法和变量。
- 优点是可以避免变量的污染
- 缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
在js中,函数即闭包,函数才会产生作用域的概念。
1.变量作用域
变量作用域两种:全局变量、局部变量。js中函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量。
2.如何从外部读取函数内部的变量?
function f1(){
var n = 123;
function f2(){ //f2是一个闭包
alert(n)
}
return f2;
}
js链式作用域:子对象会一级一级向上寻找所有父对象的变量,反之不行。
f2可以读取f1中的变量,只要把f2作为返回值,就可以在f1外读取f1内部变量
3.闭包概念
能够读取其他函数内部变量的函数。
或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。
4.闭包用途
读取函数内部的变量
让这些变量的值始终保持在内存中。不会在f1调用后被自动清除。
方便调用上下文的局部变量。利于代码封装。
原因:f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖f1,因此f1也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。
5.优缺点及解决办法
优点:
- 避免全局变量的污染
- 能够读取函数内部的变量
- 可以在内存中维护一个变量
缺点:
-
闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
-
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
-
闭包,不会在调用结束后被垃圾回收机制回收
7.函数的柯里化
最后悔的一个问题,在学习react的时候曾经看过类似问题,可是面试时感觉自己还是没有具体的说出来
什么是函数柯里化?
函数柯里化是一种技术,一种将多入参函数变成单入参函数。
这样做会让函数变得更复杂,但同时也提升了函数的普适性。
示例1
//正常函数
function sum(a,b){
console.log(a+b);
}
sum(1,2); //输出3
sum(1,3); //输出4
//柯里化函数
function curry(a){
return (b) =>{
console.log(a+b)
}
}
const sum = curry(1);
sum(2); //输出3
sum(3); //输出4
示例2
//函数柯里化封装(这个封装可以直接复制走使用)
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function () {
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this, fn, newArgs);
} else {
return fn.apply(this, newArgs);
}
}
}
//需要被柯里化的函数
function multiFn(a, b, c) {
return a * b * c;
}
//multi是柯里化之后的函数
var multi = curry(multiFn);
console.log(multi(2)(3)(4));
console.log(multi(2, 3, 4));
console.log(multi(2)(3, 4));
console.log(multi(2, 3)(4));
柯里化的应用场景
其实柯里化大多是情况下是为了减少重复传递的不变参数。
举个最简单的例子吧。手机号正则校验。
//校验手机号
function validatePhone(regExp,warn,phone){
const reg = regExp;
if (phone && reg.test(phone) === false) {
return Promise.reject(warn);
}
return Promise.resolve();
}
//调用校验
validatePhone(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符",187****3311)
这种写法乍一看好像没什么问题。但是,如果你需要多次调用呢?
//调用校验
validatePhone(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符",137****1234)
//调用校验
validatePhone(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符",159****6204)
//调用校验
validatePhone(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符",137****2125)
//调用校验
validatePhone(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符",191****5236)
会发现,正则和提示入参是固定的。很冗余。
我们可以使用我们上面封装的柯里化工具(curry函数)进行如下修改。
//完成柯里化
const curryValid = curry(validatePhone);
const validatePhoneCurry =curryValid(/^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\d{8}$/,"手机号格式不符");
//调用柯里化之后的函数
validatePhoneCurry(159****6204);
validatePhoneCurry(137****1234);
validatePhoneCurry(137****2125);
validatePhoneCurry(191****5236);
高阶函数
高阶函数就是输入参数里有函数,或者输出是函数的函数。
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数:
- 若A的数,接受的参数是一个函数。那么A函数可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map、bind等等 函数的柯里化:通过函数调用继续返回函数的方式。实现多次接收参数最后统一处理的函数编码形式。
8.ES6有哪些新特性
1.新的变量声明const、let
ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部)
let和var声明的区别:
var x = '全局变量';
{
let x = '局部变量';
console.log(x); // 局部变量
}
console.log(x); // 全局变量
let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:
const a = 1
a = 0 //报错
如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:
const student = { name: 'cc' }
student.name = 'yy';// 不报错
student = { name: 'yy' };// 报错
有几个点需要注意:
- let 关键词声明的变量不具备变量提升(hoisting)特性
- let 和 const 声明只在最靠近的一个块中(花括号内)有效
- 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
- const 在声明时必须被赋值
2.模板字符串
在ES6之前,我们往往这么处理模板字符串:
通过“\”和“+”来构建模板
$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");
而对ES6来说
- 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
- ES6反引号(``)直接搞定;
$("body").html(`This demonstrates the output of HTML content to the page,
including student's ${name}, ${seatNumber}, ${sex} and so on.`);
3.箭头函数(Arrow Functions)
ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;
箭头函数最直观的三个特点。
- 不需要 function 关键字来创建函数
- 省略 return 关键字
- 继承当前上下文的 this 关键字
// ES5
var add = function (a, b) {
return a + b;
};
// 使用箭头函数
var add = (a, b) => a + b;
// ES5
[1,2,3].map((function(x){
return x + 1;
}).bind(this));
// 使用箭头函数
[1,2,3].map(x => x + 1);
细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;
4. 函数的参数默认值
在ES6之前,我们往往这样定义参数的默认值:
// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
printText();// default
5.展开操作符Spread / Rest (…)
Spread / Rest 操作符指的是 …,具体是 Spread 还是 Rest 需要看上下文语境。
当被用于迭代器中时,它是一个 Spread 操作符:
function foo(x,y,z) {
console.log(x,y,z);
}
let arr = [1,2,3];
foo(...arr); // 1 2 3
当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:
function foo(...args) {
console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
6.二进制和八进制字面量
ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2
7.对象和数组解构
// 对象
const student = {
name: 'Sam',
age: 22,
sex: '男'
}
// 数组
// const student = ['Sam', 22, '男'];
// ES5;
const name = student.name;
const age = student.age;
const sex = student.sex;
console.log(name + ' --- ' + age + ' --- ' + sex);
// ES6
const { name, age, sex } = student;
console.log(name + ' --- ' + age + ' --- ' + sex);
8.对象超类
ES6 允许在对象中使用 super 方法:
var parent = {
foo() {
console.log("Hello from the Parent");
}
}
var child = {
foo() {
super.foo();
console.log("Hello from the Child");
}
}
Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
// Hello from the Child
9.for…of 和 for…in
for…of 用于遍历一个迭代器,如数组:
let letters = ['a', 'b', 'c'];
letters.size = 3;
for (let letter of letters) {
console.log(letter);
}
// 结果: a, b, c
for…in 用来遍历对象中的属性:
let stus = ["Sam", "22", "男"];
for (let stu in stus) {
console.log(stus[stu]);
}
// 结果: Sam, 22, 男
10.ES6中的类
ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
class Student {
constructor() {
console.log("I'm a student.");
}
study() {
console.log('study!');
}
static read() {
console.log("Reading Now.");
}
}
console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."
类中的继承和超集:
class Phone {
constructor() {
console.log("I'm a phone.");
}
}
class MI extends Phone {
constructor() {
super();
console.log("I'm a phone designed by xiaomi");
}
}
let mi8 = new MI();
extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。
有几点值得注意的是:
- 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
- 在类中定义函数不需要使用 function 关键词
11.import和export
ES6 通过 export 和export default 导出模块。导出的含义是向外暴露、输出,在一个文件中通过 import 导入另一个文件,通过变量即可以接收到导出的数据了。一般情况下,JS文件可以向外输出变量、函数和对象。
let name = 'ren',age = 12;
export {name,age};
//注意:变量需要用大括号包裹,然后才能向外输出
如果仅需向外暴露一个变量:
export var name = 'ren';
使用 export 向外输出函数的用法和变量相同,这里不再举例。
总结:使用 export 向外输出成员时,可以同时输出多个,并且必须用‘{}’大括号包裹,在其他地方使用 import 导入时,接收成员的变量名必须和这里输出的名称一致,同时,可以根据实际情况,仅接收实际需要的的成员(接收的时候也要用大括号包裹)。
如果希望通过 export 向外暴露成员,并且在导入的时候自定义接收名称,那么你可以使用 as 关键字重命名。
let name = 'ren', age = 12;
export {name, age};
import {name as myName, age as myAge} from 'url';
与 export 相比,export default 有以下几点不同:首先,在同一个模块中,export default 只允许向外暴露一次成员;然后,这种方式可以使用任意的名称接收,不像 export 那样有严格的要求;最后,export 和 export default 可以在同一模块中同时存在。
let person = {name:'ren'};
let age = 12;
let address = 'cd';
export default person;
export {age};
export {address};
import man,{age,address} from 'url'
9.js如何实现排序
简单的排序方法
在最开始,我们先了解最基本的排序方法:sort()
。这是 JavaScript 原生的一个函数,可以通过传入一个比较函数来实现对数组对象的排序。比较函数接收两个参数 a 和 b,如果 a 应该排在 b 的前面就返回负数,如果 a 应该排在 b 的后面就返回正数,如果 a 和 b 的位置无所谓就返回 0。
// 按照 age 属性进行排序
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 20 },
{ name: 'Charlie', age: 30 }
];
people.sort((a, b) => a.age - b.age);
console.log(people);
/*
Output: [
{ name: 'Bob', age: 20 },
{ name: 'Alice', age: 25 },
{ name: 'Charlie', age: 30 }
]
*/
这里的比较函数 (a, b) => a.age - b.age
可以理解为“按照 age 升序排列”。如果想要按照降序排列,只需要将函数改为 (a, b) => b.age - a.age
即可。
实现多重排序
如果要按照多个属性进行排序,我们可以在比较函数中添加更多的逻辑。例如,假设我们想先按照 age 排序,然后按照 name 排序。可以这样实现:
// 按照 age 和 name 属性进行排序
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 20 },
{ name: 'Charlie', age: 30 },
{ name: 'Bob', age: 15 },
{ name: 'Alice', age: 20 }
];
people.sort((a, b) => {
if (a.age !== b.age) {
return a.age - b.age;
} else {
return a.name.localeCompare(b.name);
}
});
console.log(people);
/*
Output: [
{ name: 'Bob', age: 15 },
{ name: 'Alice', age: 20 },
{ name: 'Bob', age: 20 },
{ name: 'Alice', age: 25 },
{ name: 'Charlie', age: 30 }
]
*/
这里的比较函数先判断两个元素的 age 属性是否相同,如果不同就按照 age 排序;如果相同,再按照 name 排序。注意到这里用了 localCompare()
方法来对字符串进行排序。
具有通用性的排序方法
上面的方法虽然简单易懂,但是当我们需要对多个不同的数组进行排序时,每次都写一个比较函数会显得很麻烦。这时候,我们可以使用一个具有通用性的排序方法 sortBy()
。
sortBy()
方法接收两个参数:要排序的数组,以及一个包含排序规则的数组。排序规则数组中的每个元素表示一个排序规则,它本身就是一个由属性名和排序方向组成的数组。
下面是一个简单的实现:
/**
* 对数组对象的指定属性进行排序
* @param {Array} arr 数组对象
* @param {Array} rules 排序规则,每个元素都是形如 ['key', 'asc'] 或者 ['key', 'desc'] 的数组
*/
function sortBy(arr, rules) {
return arr.sort((a, b) => {
for (let i = 0; i < rules.length; i++) {
const [key, direction] = rules[i];
const order = direction === 'desc' ? -1 : 1;
if (a[key] < b[key]) {
return -1 * order;
}
if (a[key] > b[key]) {
return 1 * order;
}
}
return 0;
});
}
使用方法也很简单。假设我们有一个数组对象,每个对象都包含 name、age 和 score 三个属性。我们想先按照 score 降序排列,然后按照 age 升序排列。只需要这样调用:
const students = [
{ name: 'Alice', age: 20, score: 90 },
{ name: 'Bob', age: 25, score: 85 },
{ name: 'Charlie', age: 22, score: 95 },
{ name: 'David', age: 25, score: 85 }
];
const result = sortBy(students, [
['score', 'desc'],
['age', 'asc']
]);
console.log(result);
/*
Output: [
{ name: 'Charlie', age: 22, score: 95 },
{ name: 'Bob', age: 25, score: 85 },
{ name: 'David', age: 25, score: 85 },
{ name: 'Alice', age: 20, score: 90 }
]
*/
sortBy()
方法可以轻松地实现多重排序,而且相对于每次写比较函数更加方便。不仅如此,还可以扩展到更复杂的排序需求中。例如,如果我们想按照年龄在 20 到 30 岁之间的学生先排列,然后再按照分数排序,只需要传入这样的规则:
const result = sortBy(students, [
[(student) => student.age >= 20 && student.age <= 30, 'asc'],
['score', 'desc']
]);
console.log(result);
/*
Output: [
{ name: 'Alice', age: 20, score: 90 },
{ name: 'Charlie', age: 22, score: 95 },
{ name: 'Bob', age: 25, score: 85 },
{ name: 'David', age: 25, score: 85 }
]
*/
这里使用了一个匿名函数 (student) => student.age >= 20 && student.age <= 30
,用于判断当前元素是否符合条件。如果符合条件就返回 true,否则返回 false。这个函数可以根据具体需求进行修改。
使用 Lodash 库
最后介绍一下 Lodash 库,它是一个 JavaScript 实用工具库,提供了很多方便的数组操作方法,包括排序。如果你不介意引入一个外部库的话,Lodash 是一个非常好用的选择。
假设我们还是要对上面的学生数组进行排序。使用 Lodash 可以这样实现:
import _ from 'lodash';
const sortedStudents = _.orderBy(students, ['score', 'age'], ['desc', 'asc']);
console.log(sortedStudents);
/*
Output: [
{ name: 'Charlie', age: 22, score: 95 },
{ name: 'Bob', age: 25, score: 85 },
{ name: 'David', age: 25, score: 85 },
{ name: 'Alice', age: 20, score: 90 }
]
*/
这里的 orderBy()
方法接收三个参数:要排序的数组,以及一个包含排序属性的数组和一个包含排序方向的数组。属性数组中的元素表示按照哪些属性进行排序,而方向数组中的元素表示每个属性对应的排序方向。
如果要对多重属性进行不同的排序方向,可以这样写:
const sortedStudents = _.orderBy(students, [
(student) => student.age >= 20 && student.age <= 30,
'score'
], ['asc', 'desc']);
console.log(sortedStudents);
/*
Output: [
{ name: 'Alice', age: 20, score: 90 },
{ name: 'Charlie', age: 22, score:95 },
{ name: 'David', age: 25, score: 85 },
{ name: 'Bob', age: 25, score: 85 }
]
*/
这里的第一个元素是一个函数,用于判断当前元素是否符合条件。如果符合条件就返回 true,否则返回 false。
总的来说,Lodash 提供了非常方便的数组排序方法,通过引入 Lodash 可以大大简化我们的排序操作。
结论
实现对数组对象进行排序并不是很复杂,但是有几个需要注意的点:
- 如果只需要对单个属性进行排序,可以使用 JavaScript 原生的
sort()
方法; - 如果需要对多个属性进行排序,可以在比较函数中添加更多逻辑或者使用一个通用的排序方法;
- 如果对多个数组进行排序,可以考虑使用 Lodash 库。
10.数组常见的方法
数组方法概括
方法名 | 对应版本 | 功能 | 原数组是否改变 |
---|---|---|---|
concat() | ES5- | 合并数组,并返回合并之后的数据 | n |
join() | ES5- | 使用分隔符,将数组转为字符串并返回 | n |
pop() | ES5- | 删除最后一位,并返回删除的数据 | y |
shift() | ES5- | 删除第一位,并返回删除的数据 | y |
unshift() | ES5- | 在第一位新增一或多个数据,返回长度 | y |
push() | ES5- | 在最后一位新增一或多个数据,返回长度 | y |
reverse() | ES5- | 反转数组,返回结果 | y |
slice() | ES5- | 截取指定位置的数组,并返回 | n |
sort() | ES5- | 排序(字符规则),返回结果 | y |
splice() | ES5- | 删除指定位置,并替换,返回删除的数据 | y |
toString() | ES5- | 直接转为字符串,并返回 | n |
valueOf() | ES5- | 返回数组对象的原始值 | n |
indexOf() | ES5 | 查询并返回数据的索引 | n |
lastIndexOf() | ES5 | 反向查询并返回数据的索引 | n |
forEach() | ES5 | 参数为回调函数,会遍历数组所有的项,回调函数接受三个参数,分别为value,index,self;forEach没有返回值 | n |
map() | ES5 | 同forEach,同时回调函数返回数据,组成新数组由map返回 | n |
filter() | ES5 | 同forEach,同时回调函数返回布尔值,为true的数据组成新数组由filter返回 | n |
every() | ES5 | 同forEach,同时回调函数返回布尔值,全部为true,由every返回true | n |
some() | ES5 | 同forEach,同时回调函数返回布尔值,只要由一个为true,由some返回true | n |
reduce() | ES5 | 归并,同forEach,迭代数组的所有项,并构建一个最终值,由reduce返回 | n |
reduceRight() | ES5 | 反向归并,同forEach,迭代数组的所有项,并构建一个最终值,由reduceRight返回 | n |
方法详解
1.concat()
**功能:**用于连接两个或多个数组,该方法不会改变现有的数组
,而仅仅会返回被连接数组的一个副本。
**参数:**concat(data1,data2,…);所有参数可选,要合并的数据;data为数组时,将data合并到原数组;data为具体数据时直接添加到原数组尾部;省略时创建原数组的副本。
var arr1 = [1,2,3]
var arr2 = arr1.concat();
console.log(arr1); //[1,2,3]---原数组
console.log(arr1 === arr2); //false
console.log(arr2); //[1,2,3]---原数组的副本
console.log(arr1.concat("hello","world")); //[1,2,3,"hello","world"]
console.log(arr1.concat(["a","b"],[[3,4],{"name":"admin"}])); //[1,2,3,"a","b",[3,4],{"name":"admin"}]
console.log(arr1); //[1,2,3]---原数组未改变
2.join()
**功能:**根据指定分隔符将数组中的所有元素放入一个字符串,并返回这个字符串。原数组未改变
**参数:**join(str);参数可选,默认为","号,以传入的字符作为分隔符。
var arr = [1,2,3];
console.log(arr.join()); //1,2,3
console.log(arr.join("-")); //1-2-3
console.log(arr); //[1,2,3]---原数组未改变
3.pop()
**功能:**方法用于删除并返回数组的最后一个元素。原数组改变
**参数:**无
var arr = [1,2,3];
console.log(arr.pop()); //3
console.log(arr); //[1,2]---原数组改变
4.shift()
**功能:**方法用于删除并返回数组的第一个元素。原数组改变
**参数:**无
var arr = [1,2,3]
console.log(arr.shift()); //1
console.log(arr); //[2,3]---原数组改变
5.unshift()
**功能:**向数组的开头添加一个或更多元素,并返回新的长度。原数组改变
**参数:**unshift(newData1, newData2, …)
var arr = [1,2,3];
console.log(arr.unshift("hello")); //4
console.log(arr); //["hello",1,2,3]---原数组改变
console.log(arr.unshift("a","b")); //6
console.log(arr); //["a","b","hello",1,2,3]---原数组改变
6.push()
**功能:**向数组的末尾添加一个或更多元素,并返回新的长度。原数组改变
**参数:**push(newData1, newData2, …)
var arr = [1,2,3];
console.log(arr.push("hello")); //4
console.log(arr); //[1,2,3,"hello"]---原数组改变
console.log(arr.push("a","b")); //6
console.log(arr); //[1,2,3,"hello","a","b"]---原数组改变
7.reverse()
**功能:**颠倒数组中元素的顺序。原数组改变
**参数:**无
var arr = [1,2,3];
console.log(arr.reverse()); //[3,2,1]
console.log(arr); //[3,2,1]---原数组改变
8.slice()
**功能:**可从已有的数组中返回选定的元素。该方法接收两个参数slice(start,end),strat为必选,表示从第几位开始;end为可选,表示到第几位结束(不包含end位),省略表示到最后一位;start和end都可以为负数,负数时表示从最后一位开始算起,如-1表示最后一位。
原数组未改变
**参数:**slice(startIndex, endIndex)
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.slice(1,3)); //["Jack","Lucy"]
console.log(arr.slice(1)); //["Jack","Lucy","Lily","May"]
console.log(arr.slice(-4,-1)); //["Jack","Lucy","Lily"]
console.log(arr.slice(-2)); //["Lily","May"]
console.log(arr.slice(1,-2)); //["Jack","Lucy"]
console.log(arr); //["Tom","Jack","Lucy","Lily","May"]---原数组未改变
9.sort()
**功能:**对数组中的元素进行排序,默认是升序。原数组改变
var arr = [6,1,5,2,3];
console.log(arr.sort()); //[1, 2, 3, 5, 6]
console.log(arr); //[1, 2, 3, 5, 6]---原数组改变
但是在排序前,会先调用数组的toString方法,将每个元素都转成字符之后,再进行排序,此时会按照字符串的排序,逐位比较,进行排序。
var arr = [6,1024,52,256,369];
console.log(arr.sort()); //[1024, 256, 369, 52, 6]
console.log(arr); //[1024, 256, 369, 52, 6]---原数组改变
**参数:**sort(callback)
如果需要按照数值排序,需要传参。sort(callback),callback为回调函数,该函数应该具有两个参数,比较这两个参数,然后返回一个用于说明这两个值的相对顺序的数字(a-b)。其返回值如下:
若 a 小于 b,返回一个小于 0 的值。
若 a 等于 b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。
var arr = [6,1024,52,256,369];
console.log(arr.sort(fn)); //[6, 52, 256, 369, 1024]
console.log(arr); //[6, 52, 256, 369, 1024]---原数组改变
function fn(a,b){
return a-b;
}
10.splice()
**功能:**向数组中添加,或从数组删除,或替换数组中的元素,然后返回被删除/替换的元素。
**参数:**splice(start,num,data1,data2,…); 所有参数全部可选。
1>不传参时:无操作
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice()); //[]
console.log(arr); //["Tom","Jack","Lucy","Lily","May"]---无操作
2>只传入start:表示从索引为start的数据开始删除,直到数组结束
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2)); //["Lucy", "Lily", "May"]
console.log(arr); //["Tom", "Jack"]---原数组改变
3>传入start和num:表示从索引为start的数据开始删除,删除num个
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,2)); //["Lucy", "Lily"]
console.log(arr); //["Tom", "Jack", "May"]---原数组改变
4>传入更多:表示从索引为start的数据开始删除,删除num个,并将第三个参数及后面所有参数,插入到start的位置
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,2,"a","b")); //["Lucy", "Lily"]
console.log(arr); //["Tom", "Jack", "a", "b", "May"]---原数组改变
5>传入更多:表示从索引为start的数据开始删除,删除num个,并将第三个参数及后面所有参数,插入到start的位置
var arr = ["Tom","Jack","Lucy","Lily","May"];
console.log(arr.splice(2,0,"a","b")); //[]
console.log(arr); //["Tom", "Jack", "a", "b", "Lucy", "Lily", "May"]---原数组改变
11.toString()
**功能:**转换成字符串,类似于没有参数的join()。该方法会在数据发生隐式类型转换时被自动调用,如果手动调用,就是直接转为字符串。
**参数:**无
var arr = [1,2,3];
console.log(arr.toString()); //1,2,3
console.log(arr); //[1,2,3]---原数组未改变
12.valueOf()
**功能:**返回数组的原始值(一般情况下其实就是数组自身),一般由js在后台调用,并不显式的出现在代码中
**参数:**无
var arr = [1,2,3];
console.log(arr.valueOf()); //[1,2,3]
console.log(arr); //[1,2,3]
//为了证明返回的是数组自身
console.log(arr.valueOf() == arr); //true
13.indexOf()
**功能:**根据指定的数据,从左向右,查询在数组中出现的位置,如果不存在指定的数据,返回-1。该方法是查询方法,不会对数组产生改变。
**参数:**indexOf(value, start);value为要查询的数据;start为可选,表示开始查询的位置,当start为负数时,从数组的尾部向前数;如果查询不到value的存在,则方法返回-1
var arr = ["h","e","l","l","o"];
console.log(arr.indexOf("l")); //2
console.log(arr.indexOf("l",3)); //3
console.log(arr.indexOf("l",4)); //-1
console.log(arr.indexOf("l",-1)); //-1
console.log(arr.indexOf("l",-3)); //2
14.lastIndexOf()
**功能:**根据指定的数据,从右向左,查询在数组中出现的位置,如果不存在指定的数据,返回-1。该方法是查询方法,不会对数组产生改变。
**参数:**lastIndexOf(value, start);value为要查询的数据;start为可选,表示开始查询的位置,当start为负数时,从数组的尾部向前数;如果查询不到value的存在,则方法返回-1
var arr = ["h","e","l","l","o"];
console.log(arr.lastIndexOf("l")); //3
console.log(arr.lastIndexOf("l",3)); //3
console.log(arr.lastIndexOf("l",1)); //-1
console.log(arr.lastIndexOf("l",-3)); //2
console.log(arr.lastIndexOf("l",-4)); //-1
15.forEach()
**功能:**ES5新增方法,用来遍历数组,该方法没有返回值。forEach接收的回调函数会根据数组的每一项执行,该回调函数默认有三个参数,分别为:遍历到的数组的数据,对应的索引,数组自身。
**参数:**forEach(callback);callback默认有三个参数,分别为value,index,self。
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.forEach(function(value,index,self){
console.log(value + "--" + index + "--" + (arr === self));
})
// 打印结果为:
// Tom--0--true
// Jack--1--true
// Lucy--2--true
// Lily--3--true
// May--4--true
console.log(a); //undefined---forEach没有返回值
//该方法为遍历方法,不会修改原数组
16.map()
**功能:**1.同forEach功能;2.map的回调函数会将执行结果返回,最后map将所有回调函数的返回值组成新数组返回。
**参数:**map(callback);callback默认有三个参数,分别为value,index,self。
//功能1:同forEach
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.map(function(value,index,self){
console.log(value + "--" + index + "--" + (arr === self))
})
// 打印结果为:
// Tom--0--true
// Jack--1--true
// Lucy--2--true
// Lily--3--true
// May--4--true
//功能2:每次回调函数的返回值被map组成新数组返回
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.map(function(value,index,self){
return "hi:"+value;
})
console.log(a); //["hi:Tom", "hi:Jack", "hi:Lucy", "hi:Lily", "hi:May"]
console.log(arr); //["Tom", "Jack", "Lucy", "Lily", "May"]---原数组未改变
17.filter()
**功能:**1.同forEach功能;2.filter的回调函数需要返回布尔值,当为true时,将本次数组的数据返回给filter,最后filter将所有回调函数的返回值组成新数组返回(此功能可理解为“过滤”)。
**参数:**filter(callback);callback默认有三个参数,分别为value,index,self。
//功能1:同forEach
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.filter(function(value,index,self){
console.log(value + "--" + index + "--" + (arr === self))
})
// 打印结果为:
// Tom--0--true
// Jack--1--true
// Lucy--2--true
// Lily--3--true
// May--4--true
//功能2:当回调函数的返回值为true时,本次的数组值返回给filter,被filter组成新数组返回
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.filter(function(value,index,self){
return value.length > 3;
})
console.log(a); //["Jack", "Lucy", "Lily"]
console.log(arr); //["Tom", "Jack", "Lucy", "Lily", "May"]---原数组未改变
18.every()
**功能:**判断数组中每一项是否都满足条件,只有所有项都满足条件,才会返回true。
**参数:**every()接收一个回调函数作为参数,这个回调函数需要有返回值,every(callback);callback默认有三个参数,分别为value,index,self。
**功能1:**当回调函数的返回值为true时,类似于forEach的功能,遍历所有;如果为false,那么停止执行,后面的数据不再遍历,停在第一个返回false的位置。
//demo1:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.every(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
})
// 打印结果为:
// Tom--0--true
//因为回调函数中没有return true,默认返回undefined,等同于返回false
//demo2:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.every(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return value.length < 4;
})
// 打印结果为:
// Tom--0--true
// abc--1--true
// Jack--2--true
//因为当遍历到Jack时,回调函数到return返回false,此时Jack已经遍历,但是后面数据就不再被遍历了
//demo3:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.every(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return true;
})
// 打印结果为:
// Tom--0--true
// abc--1--true
// Jack--2--true
// Lucy--3--true
// Lily--4--true
// May--5--true
//因为每个回调函数的返回值都是true,那么会遍历数组所有数据,等同于forEach功能
**功能2:**当每个回调函数的返回值都为true时,every的返回值为true,只要有一个回调函数的返回值为false,every的返回值都为false
//demo1:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.every(function(value,index,self){
return value.length > 3;
})
console.log(a); //false
//demo2:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.every(function(value,index,self){
return value.length > 2;
})
console.log(a); //true
19.some()
**功能:**判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
**参数:**some()接收一个回调函数作为参数,这个回调函数需要有返回值,some(callback);callback默认有三个参数,分别为value,index,self。
**功能1:**因为要判断数组中的每一项,只要有一个回调函数返回true,some都会返回true,所以与every正好相反,当遇到一个回调函数的返回值为true时,可以确定结果,那么停止执行,后面都数据不再遍历,停在第一个返回true的位置;当回调函数的返回值为false时,需要继续向后执行,到最后才能确定结果,所以会遍历所有数据,实现类似于forEach的功能,遍历所有。
//demo1:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return value.length > 3;
})
// 打印结果为:
// Tom--0--true
// abc--1--true
// Jack--2--true
//demo2:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return true;
})
// 打印结果为:
// Tom--0--true
//demo3:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return false;
})
// 打印结果为:
// Tom--0--true
// abc--1--true
// Jack--2--true
// Lucy--3--true
// Lily--4--true
// May--5--true
**功能2:**与every相反,只要有一个回调函数的返回值都为true,some的返回值为true,所有回调函数的返回值为false,some的返回值才为false
//demo1:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
return value.length > 3;
})
console.log(a); //true
//demo2:
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
return value.length > 4;
})
console.log(a); //false
19.reduce()
**功能:**从数组的第一项开始,逐个遍历到最后,迭代数组的所有项,然后构建一个最终返回的值。
**参数:**reduce()接收一个或两个参数:第一个是回调函数,表示在数组的每一项上调用的函数;第二个参数(可选的)作为归并的初始值,被回调函数第一次执行时的第一个参数接收。
reduce(callback,initial);callback默认有四个参数,分别为prev,now,index,self。
callback返回的任何值都会作为下一次执行的第一个参数。
如果initial参数被省略,那么第一次迭代发生在数组的第二项上,因此callback的第一个参数是数组的第一项,第二个参数就是数组的第二项。
//demo1:不省略initial参数,回调函数没有返回值
var arr = [10,20,30,40,50];
arr.reduce(function(prev,now,index,self){
console.log(prev + "--" + now + "--" + index + "--" + (arr == self))
}, 2019)
// 打印结果为:
// 2019--10--0--true
// undefined--20--1--true
// undefined--30--2--true
// undefined--40--3--true
// undefined--50--4--true
// 此时回调函数没有return,所以从第二次开始,prev拿到的是undefined
//demo2:省略initial参数,回调函数没有返回值
var arr = [10,20,30,40,50];
arr.reduce(function(prev,now,index,self){
console.log(prev + "--" + now + "--" + index + "--" + (arr == self))
})
// 打印结果为:第一次,回调函数的第一个参数是数组的第一项。第二个参数就是数组的第二项
// 10--20--1--true
// undefined--30--2--true
// undefined--40--3--true
// undefined--50--4--true
// 此时回调函数没有return,所以从第二次开始,prev拿到的是undefined
//demo3:不省略initial参数,回调函数有返回值
var arr = [10,20,30,40,50];
arr.reduce(function(prev,now,index,self){
console.log(prev + "--" + now + "--" + index + "--" + (arr == self));
return "hello";
}, 2019)
// 打印结果为:
// 2019--10--0--true
// hello--20--1--true
// hello--30--2--true
// hello--40--3--true
// hello--50--4--true
// 此时回调函数有return,所以从第二次开始,prev拿到的是回调函数return的值
//demo4:省略initial参数,回调函数有返回值
var arr = [10,20,30,40,50];
arr.reduce(function(prev,now,index,self){
console.log(prev + "--" + now + "--" + index + "--" + (arr == self));
return "hello";
})
// 打印结果为:第一次,回调函数的第一个参数是数组的第一项。第二个参数就是数组的第二项
// 10--20--1--true
// hello--30--2--true
// hello--40--3--true
// hello--50--4--true
// 此时回调函数有return,所以从第二次开始,prev拿到的是回调函数return的值
//demo5:使用reduce计算数组中所有数据的和
var arr = [10,20,30,40,50];
var sum = arr.reduce(function(prev,now,index,self){
return prev + now;
})
console.log(sum); //150
// 回调函数的最后一次return的结果被返回到reduce方法的身上
//demo6:使用reduce计算数组中所有数据的和
var arr = [10,20,30,40,50];
var sum = arr.reduce(function(prev,now,index,self){
return prev + now;
}, 8)
console.log(sum); //158
// 回调函数的最后一次return的结果被返回到reduce方法的身上
// 因为reduce有第二个参数initial,在第一次执行时被计算,所以最终结果被加上8
20.reduceRight()
功能:(与reduce类似)从数组的最后一项开始,向前逐个遍历到第一位,迭代数组的所有项,然后构建一个最终返回的值。
**参数:**同reduce。
**demo:**同reduce
11.foreach和map的区别
共同点
- 都是循环遍历数组中的每一项
- 每一次执行匿名函数都支持三个参数,数组中的当前项item,当前项的索引index,原始数组input
- 匿名函数中的this都是指window
- 只能遍历数组
能用forEach()做到循环的,map()同样也可以做到循环。反过来也是如此。
不同点
- forEach()方法不会返回执行结果,而是undefined。forEach() 被调用时,不会改变原数组,也就是调用它的数组(尽管 callback 函数在被调用时可能会改变原数组)。
- map()方法会分配内存空间存储新数组并返回,map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)
12.如何渲染几万条以上数量比较多的数据
思路:
- 减少dom操作:dom操作是非常损耗性能的,如果能够在保证单次dom操作任务量比较少(控制在短时间内能够完成)的前提下还能减少dom的操作,就能够极大程度的提升运行速度;
- 分时插入:js单线程机制导致如果一个时间段内,一个任务所需要的耗时越久,则会导致其他任务无法进行,比如我们一直去插入数据,导致渲染这件事情没有办法去做,则会表现出无内容更新、卡住了;我们通过分配时间去操作,比如在不影响用户观看的前提下,把任务分解成多个时间去做,简单来说就是先去插入一部分,然后渲染一部分,这就考虑到;
做法:
- 定时器分批加载
- web worker API 异步处理
- 后端处理
- 优化算法
13.Vue的生命周期
组件从 创建 到 销毁 的整个过程就是生命周期
创建期间的生命周期函数:
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好data 和 methods 属性
- created:实例已经完成了模板的编译,但是还没有挂载到页面中
- beforeMount:此时已经完成了模板的翻译,但是还有完全挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
运行期间的生命周期函数:
- beforeUpdate:状态更新之前执行此函数,此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用次函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
销毁期间的生命周期函数:
-
beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用
- 当执行 beforeDestroy 钩子函数的时候,Vue实例就已经从运行阶段进入到了销毁阶段;当执行 beforeDestroy 的时候,实例身上所有的 data 和所有的 methods, 以及 过滤器、指令、、 都处于可用状态,此时,还没有真正执行销毁的过程
-
destroyed:Vue 实例销毁后调用。调用后,vue 实例 指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁
14.发送请求一般写在那个生命周期里面
mounted或者created都可以
vue是数据驱动的,也就是说只要我能操作到data中的数据时即可请求。因此mounted或者created都可以。
区别在于:created时dom还没有加载,适合调用一些对dom起作用的方法从后台获取数据,如果对DOM操作一定要在该生命周期中,那么需要放在Vue.nextTick()
的回调函数中。
mounted时dom已经加载完毕,适合调用一些对dom起补充作用的方法从后台获取数据。
理论上后者的时候可能会重复更新 DOM,有那么一点性能损耗,但考虑到网络请求的异步特性,最终落实到产品当中的微乎其微。
15.输入一个url处理的完整流程
1.浏览器解析url
2.域名解析
3.建立tcp连接
4.发送http请求
5.1处理http请求
5.2发送http相应
6.浏览器显示页面
7.断开tcp连接
具体详细的讲解参考:https://juejin.cn/post/7233560217324830780?share_token=09c0f2e4-efc9-4587-93c6-97db39e863e2
16.常见的请求头有哪些
请求头
请求头 | 描述 | 示例 |
---|---|---|
Accept | 用户代理支持的MIME类型列表 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9 |
Accept-Encoding | 用户代理支持的压缩方法(优先级) | Accept-Encoding: br, gzip, deflate |
Accept-Language | 用户代理期望的语言(优先级) | Accept-Language: zh-CN,zh;q=0.9 |
Authorization | 用于超文本传输协议的认证的认证信息 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 缓存机制 | Cache-Control: max-age=0 |
Connection | 是否持久连接 | Connection: keep-alive |
Cookie | HTTP cookies | 服务器通过Set-Cookie存储到客户端的 Cookie |
Host | 主机名 + 端口号 | Host: 127.0.0.1:8080 |
If-Match | 请求指定标识符资源 | If-Match: "56a88df57772gt555gr5469a32ee75d65dcwq989" |
If-Modified-Since | 请求指定时间修改过的资源 | If-Modified-Since: Wed, 19 Oct 2020 17:32:00 GMT |
If-None-Match | 请求非指定标识符资源 | If-None-Match: "56a88df57772gt555gr5469a32ee75d65dcwq989" |
Upgrade-Insecure-Requests | 客户端优先接受加密和有身份验证的响应,支持CSP | Upgrade-Insecure-Requests: 1 |
User-Agent | 用户代理 | User-Agent:Safari/537.36 |
Vary | 缓存策略,常用于自适应缓存配置和 SEO | Vary: User-Agent |
响应头
响应头 | 描述 | 示例 |
---|---|---|
Allow | 服务器支持哪些请求方法 | Allow: POST,GET,OPTIONS |
Cache-Control | 缓存机制 | Cache-Control: public, max-age=3600 |
Connection | 是否持久连接 | Connection: keep-alive |
Content-Encoding | 内容编码方式 | Content-Encoding: br |
Content-Type | 内容的MIME类型 | Content-Type: text/html; charset=UTF-8 |
Date | 报文创建时间 | Date: Sun, 28 Feb 2021 11:52:51 GMT |
Expires | 资源过期时间 | Expires: Sun, 28 Feb 2021 12:52:51 GMT |
ETag | 资源标识符 | ETag: "56a88df57772gt555gr5469a32ee75d65dcwq989" |
Set-Cookie | 服务端向客户端发送Cookie | Set-Cookie: token=6e204d9b-103a-431e-b8de-ba97b2d1; path=/; HttpOnly |
17.模糊查询
前端处理
1. indexof 方法
语法:stringObject.indexOf(searchvalue, fromindex)
参数:searchvalue 必需。规定需检索的字符串值。 fromindex 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。
说明:该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 searchvalue。开始检索的位置在字符串的 fromindex 处或字符串的开头(没有指定 fromindex 时)。如果找到一个 searchvalue,则返回 searchvalue 的第一次出现的位置。stringObject 中的字符位置是从 0 开始的。如果没有找到,将返回 -1。
/**
* 使用indexof方法实现模糊查询
* @param {Array} list 进行查询的数组
* @param {String} keyWord 查询的关键词
* @return {Array} 查询的结果
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].indexOf(keyWord) >= 0) {
arr.push(list[i]);
}
}
return arr;
}
2. split 方法
语法:stringObject.split(separator, howmany)
参数:separator 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。howmany 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
说明:该方法通过在 separator 指定的边界处将字符串 stringObject 分割成子串并返回子串数组。返回的数组中的字串不包括 separator 自身。如果 stringObject 中不存在 separator,将返回一个只包含stringObject的数组。故可以根据返回数组的长度来判断是否存在子字符串 separator 。
/**
* 使用spilt方法实现模糊查询
* @param {Array} list 进行查询的数组
* @param {String} keyWord 查询的关键词
* @return {Array} 查询的结果
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].split(keyWord).length > 1) {
arr.push(list[i]);
}
}
return arr;
}
3. match 方法
语法:stringObject.match(searchvalue) 或 stringObject.match(regexp)
参数:searchvalue 必需。规定要检索的字符串值。regexp 必需。规定要匹配的模式的 RegExp 对象。如果该参数不是 RegExp 对象,则需要首先把它传递给 RegExp 构造函数,将其转换为 RegExp 对象。
说明:该方法将在字符串 stringObject 内检索指定的值,或找到一个或多个正则表达式的匹配。如果没有找到任何匹配的文本,将返回 null 。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。
/**
* 使用match方法实现模糊查询
* @param {Array} list 进行查询的数组
* @param {String} keyWord 查询的关键词
* @return {Array} 查询的结果
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].match(keyWord) != null) {
arr.push(list[i]);
}
}
return arr;
}
4. test方法(正则匹配)
语法:RegExpObject.test(string)
参数:string 必需。要检测的字符串。
说明:该方法用于检测一个字符串是否匹配某个模式。如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。
/**
* 使用test方法实现模糊查询
* @param {Array} list 原数组
* @param {String} keyWord 查询的关键词
* @return {Array} 查询的结果
*/
function fuzzyQuery(list, keyWord) {
var reg = new RegExp(keyWord);
var arr = [];
for (var i = 0; i < list.length; i++) {
if (reg.test(list[i])) {
arr.push(list[i]);
}
}
return arr;
}
后端处理
在sql中,可以根据某些特定的条件进行模糊查询,比方说要查找咦某个字母开头的昵称,或者一组类似的数据等。我们用字段like表示相似,来进行模糊查询。
在模糊查询中,%表示任意字符,_表示一个字符。
下面直接上实例。
假设我们创建了一个学生表用来进行查询
--查询名字是s开头的姓名
select * from student where name like "s%";
--查询名字是s结尾的姓名
select * from student where name like "%s";
--要查询中间包含s的
select * from student where name like "%s%";
--查询名字第二个字母是s的
select * from student where name like "_s%";
--同理若查找第三个就在其前面写两个_ ,"__s%"
18.跨域问题是如何解决的
为什么会产生跨域问题?
出于浏览器的同源策略限制。
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。
当一个请求的url的协议、域名、端口三者只要有一个与当前页面的url不同则会出现跨域问题
解决方法
-
jsonp请求
- 只能处理get请求
- 在script标签中
-
CROS
- Cross-Origin Resource Sharing的缩写
- CORS新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源
-
gateway网关
-
前端的config
- vue.config.js配置代理服务器
-
后端的注解
- @CrossOrigin
-
后端配置一个config类去拦截
-
@Configuration//一定不要忽略此注解 public class CorsConfigimplementsWebMvcConfigurer{ @Override publicvoidaddCorsMappings(CorsRegistryregistry){ registry.addMapping("/**")//所有接口 .allowCredentials(true)//是否发送Cookie .allowedOriginPatterns("*")//支持域 .allowedMethods(newString[]{"GET","POST","PUT","DELETE"})//支持方法 .allowedHeaders("*") .exposedHeaders("*"); } }
-
-
nginx反向代理
- 跨域只是浏览器向服务器发送请求的时候,浏览器的限制。而服务器和服务器之间是没有跨域的限制的。
- 反向代理是利用代理服务器接收到请求之后,转发给真正的服务器,并把结果返回到浏览器上。
19.使用过Vue3吗
概览Vue3
的新特性,如下:
- 速度更快
- 体积减少
- 更易维护
- 更接近原生
- 更易使用
#速度更快
vue3相比vue2
- 重写了虚拟
Dom
实现 - 编译模板的优化
- 更高效的组件初始化
undate
性能提高1.3~2倍SSR
速度提高了2~3倍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifREWSio-1689036637070)(Resources/ac1d23d0-5087-11eb-ab90-d9ae814b240d.png)]
#体积更小
通过webpack
的tree-shaking
功能,可以将无用模块“剪辑”,仅打包需要的
能够tree-shaking
,有两大好处:
- 对开发人员,能够对
vue
实现更多其他的功能,而不必担忧整体体积过大 - 对使用者,打包出来的包体积变小了
vue
可以开发出更多其他的功能,而不必担忧vue
打包出来的整体体积过多
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tkntntyj-1689036637071)(Resources/c01af010-5087-11eb-85f6-6fac77c0c9b3.png)]
#更易维护
#compositon Api
- 可与现有的
Options API
一起使用 - 灵活的逻辑组合与复用
Vue3
模块可以和其他框架搭配使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AgJdOx5a-1689036637072)(Resources/c5c919b0-5087-11eb-ab90-d9ae814b240d.png)]
#更好的Typescript支持
VUE3
是基于typescipt
编写的,可以享受到自动的类型定义提示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wkd6tfxU-1689036637072)(Resources/cc688120-5087-11eb-ab90-d9ae814b240d.png)]
#编译器重写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-URHX6gTp-1689036637072)(Resources/fcd33800-5087-11eb-85f6-6fac77c0c9b3.png)]
#更接近原生
可以自定义渲染 API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WvgZ61Jo-1689036637073)(Resources/0c7d88a0-5088-11eb-ab90-d9ae814b240d.png)]
#更易使用
响应式 Api
暴露出来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vudabVx-1689036637073)(Resources/26070260-5088-11eb-ab90-d9ae814b240d.png)]
轻松识别组件重新渲染原因
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rObjwVmP-1689036637073)(Resources/43b2fcb0-5088-11eb-ab90-d9ae814b240d.png)]
20.TS了解过吗
Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。
增加了静态类型,可以在开发人员编写脚本时检测错误,使得代码质量更好,更健壮。
优势:
- 杜绝手误导致的变量名写错;、
- 类型可以一定程度上充当文档;
- IDE自动填充,自动联想;
我自己没有系统的学习过TS,所以这儿就直接说没了解过
21.Vuex使用场景
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
vuex使用场景:
- 当一个组件需要多次派发事件时。例如购物车数量加减。
- 跨组件共享数据、跨页面共享数据。例如订单状态更新。
- 需要持久化的数据。例如登录后用户的信息。
- 当您需要开发中大型应用,适合复杂的多模块多页面的数据交互,考虑如何更好地在组件外部管理状态时
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
22.一个表格,我如何实现按某一列进行排序
点击需要排序列的数据头,获取数据头所在列的索引。通过索引找到每一行所对应列的数据,之后对该列的所有数据进行排序(本实例只进行降序排列),排序结果的每个数据的索引就是该数据所对应行的位置。
这里我面试时候没太清楚面试官的意思,就说了冒泡排序的算法,后面就被问到用过哪些算法
这里参考JavaScript/CSS 表格排序功能 | 菜鸟教程 (runoob.com)对表格进行排序
23.选项式API和组合式API
- 在逻辑组织和逻辑复用方面,
Composition API
是优于Options API
- 因为
Composition API
几乎是函数,会有更好的类型推断。 Composition API
对tree-shaking
友好,代码也更容易压缩Composition API
中见不到this
的使用,减少了this
指向不明的情况- 如果是小型组件,可以继续使用
Options API
,也是十分友好的
24.JS的函数库使用过哪些
面试时只说了一个在处理日期的day.js
下面我就总结一些常用的js库函数
Axios: Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
lodashjs:Lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库。
BetterScroll: BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 iscroll的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。BetterScroll 是使用纯 JavaScript 实现的,这意味着它是无依赖的。
Cropper.js: cropperjs是一款非常强大却又简单的图片裁剪工具,它可以进行非常灵活的配置,支持手机端使用,支持包括IE9以上的现代浏览器
fastclick: 解决移动端点击延迟的问题
date-fns: JavaScript 日期工具类库
momentjs:JavaScript 日期处理类库
Day.js: Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样。
numeraljs:一个用于格式化和处理数字的javascript库。
qs:qs是一个url参数转化(parse和stringify)的js库。
reqwest:浏览器异步HTTP请求
immutable:JavaScript的不可变集合
events:为不具有此功能的环境(例如浏览器)实现了Node.js模块具备的功能。
classnames:一个简单的JavaScript实用程序,将多个className连接在一起。
rx.js:异步数据流编程的JavaScript版本。
clipboardjs:复制到剪切板
clipboard-copy:30行代码实现复制到剪切板操作
工具
handlebarsjs:模板引擎
yargs:yargs是nodejs环境下的命令行参数解析工具
参考链接:https://juejin.cn/post/6881949547258445838
25.用过哪些算法
这个问题我介绍了几种常见的算法,比如排序算法,贪心算法,分治算法等等
不应该称为一个问题
26.你是如何封装公共组件
先根据页面进行组件拆分,然后开始进行组件编写,路由配置等等。
按照自己写项目的时候的过程进行说明即可,不是很难的问题
27.防抖和节流知道吗
节流:在规定的时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
防抖:前面的所有触发都被取消,最后一次执行在规定的时间之后才回触发,也就是说如果连续快速的触发,只会执行一次
使用lodash.js库实现防抖和节流
(npm查找或者lodash)
节流
防抖函数
func
(Function): 要防抖动的函数。[wait=0]
(number): 需要延迟的毫秒数。[options=]
(Object): 选项对象。[options.leading=false]
(boolean): 指定在延迟开始前调用。[options.maxWait]
(number): 设置func
允许被延迟的最大值。[options.trailing=true]
(boolean): 指定在延迟结束后调用。
_.debounce(func, [wait=0], [options=])
创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟
wait
毫秒后调用func
方法。 debounced(防抖动)函数提供一个cancel
方法取消延迟的函数调用以及flush
方法立即调用。 可以提供一个 options(选项) 对象决定如何调用func
方法,options.leading
与|或options.trailing
决定延迟前后如何触发(注:是 先调用后等待 还是 先等待后调用)。func
调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。 后续调用的 debounced(防抖动)函数返回是最后一次func
调用的结果。
// 避免窗口在变动时出现昂贵的计算开销。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
// 当点击时 `sendMail` 随后就被调用。
jQuery(element).on('click', _.debounce(sendMail, 300, {
'leading': true,
'trailing': false
}));
// 确保 `batchLog` 调用1次之后,1秒内会被触发。
var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
var source = new EventSource('/stream');
jQuery(source).on('message', debounced);
// 取消一个 trailing 的防抖动调用
jQuery(window).on('popstate', debounced.cancel);
节流函数
func
(Function): 要节流的函数。[wait=0]
(number): 需要节流的毫秒。[options=]
(Object): 选项对象。[options.leading=true]
(boolean): 指定调用在节流开始前。[options.trailing=true]
(boolean): 指定调用在节流结束后。
_.throttle(func, [wait=0], [options=])
创建一个节流函数,在 wait 秒内最多执行
func
一次的函数。 该函数提供一个cancel
方法取消延迟的函数调用以及flush
方法立即调用。 可以提供一个 options 对象决定如何调用func
方法, options.leading 与|或 options.trailing 决定 wait 前后如何触发。func
会传入最后一次传入的参数给这个函数。 随后调用的函数返回是最后一次func
调用的结果。
// 避免在滚动时过分的更新定位
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
// 点击后就调用 `renewToken`,但5分钟内超过1次。
var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
jQuery(element).on('click', throttled);
// 取消一个 trailing 的节流调用。
jQuery(window).on('popstate', throttled.cancel);
28.git常用的命令
git init #初始化本地git仓库(创建新仓库)
git add . # 增加当前子目录下所有更改过的文件至index
git commit -m ‘xxx’ # 提交
git branch # 显示本地分支
git checkout -b master_copy # 从当前分支创建新分支master_copy并检出
git push origin master # 将当前分支push到远程master分支
git pull origin master # 获取远程分支master并merge到当前分支
git init # 初始化本地git仓库(创建新仓库)
git config --global user.name "xxx" # 配置用户名
git config --global user.email "xxx@xxx.com" # 配置邮件
git config --global color.ui true # git status等命令自动着色
git config --global color.status auto
git config --global color.diff auto
git config --global color.branch auto
git config --global color.interactive auto
git config --global --unset http.proxy # remove proxy configuration on git
git clone git+ssh://git@192.168.53.168/VT.git # clone远程仓库
git status # 查看当前版本状态(是否修改)
git add xyz # 添加xyz文件至index
git add . # 增加当前子目录下所有更改过的文件至index
git commit -m 'xxx' # 提交
git commit --amend -m 'xxx' # 合并上一次提交(用于反复修改)
git commit -am 'xxx' # 将add和commit合为一步
git rm xxx # 删除index中的文件
git rm -r * # 递归删除
git log # 显示提交日志
git log -1 # 显示1行日志 -n为n行
git log -5
git log --stat # 显示提交日志及相关变动文件
git log -p -m
git show dfb02e6e4f2f7b573337763e5c0013802e392818 # 显示某个提交的详细内容
git show dfb02 # 可只用commitid的前几位
git show HEAD # 显示HEAD提交日志
git show HEAD^ # 显示HEAD的父(上一个版本)的提交日志 ^^为上两个版本 ^5为上5个版本
git tag # 显示已存在的tag
git tag -a v2.0 -m 'xxx' # 增加v2.0的tag
git show v2.0 # 显示v2.0的日志及详细内容
git log v2.0 # 显示v2.0的日志
git diff # 显示所有未添加至index的变更
git diff --cached # 显示所有已添加index但还未commit的变更
git diff HEAD^ # 比较与上一个版本的差异
git diff HEAD -- ./lib # 比较与HEAD版本lib目录的差异
git diff origin/master..master # 比较远程分支master上有本地分支master上没有的
git diff origin/master..master --stat # 只显示差异的文件,不显示具体内容
git remote add origin git+ssh://git@192.168.53.168/VT.git # 增加远程定义(用于push/pull/fetch)
git branch # 显示本地分支
git branch --contains 50089 # 显示包含提交50089的分支
git branch -a # 显示所有分支
git branch -r # 显示所有原创分支
git branch --merged # 显示所有已合并到当前分支的分支
git branch --no-merged # 显示所有未合并到当前分支的分支
git branch -m master master_copy # 本地分支改名
git checkout -b master_copy # 从当前分支创建新分支master_copy并检出
git checkout -b master master_copy # 上面的完整版
git checkout features/performance # 检出已存在的features/performance分支
git checkout --track hotfixes/BJVEP933 # 检出远程分支hotfixes/BJVEP933并创建本地跟踪分支
git checkout v2.0 # 检出版本v2.0
git checkout -b devel origin/develop # 从远程分支develop创建新本地分支devel并检出
git checkout -- README # 检出head版本的README文件(可用于修改错误回退)
git merge origin/master # 合并远程master分支至当前分支
git cherry-pick ff44785404a8e # 合并提交ff44785404a8e的修改
git push origin master # 将当前分支push到远程master分支
git push origin :hotfixes/BJVEP933 # 删除远程仓库的hotfixes/BJVEP933分支
git push --tags # 把所有tag推送到远程仓库
git fetch # 获取所有远程分支(不更新本地分支,另需merge)
git fetch --prune # 获取所有原创分支并清除服务器上已删掉的分支
git pull origin master # 获取远程分支master并merge到当前分支
git mv README README2 # 重命名文件README为README2
git reset --hard HEAD # 将当前版本重置为HEAD(通常用于merge失败回退)
git rebase
git branch -d hotfixes/BJVEP933 # 删除分支hotfixes/BJVEP933(本分支修改已合并到其他分支)
git branch -D hotfixes/BJVEP933 # 强制删除分支hotfixes/BJVEP933
git ls-files # 列出git index包含的文件
git show-branch # 图示当前分支历史
git show-branch --all # 图示所有分支历史
git whatchanged # 显示提交历史对应的文件修改
git revert dfb02e6e4f2f7b573337763e5c0013802e392818 # 撤销提交dfb02e6e4f2f7b573337763e5c0013802e392818
git ls-tree HEAD # 内部命令:显示某个git对象
git rev-parse v2.0 # 内部命令:显示某个ref对于的SHA1 HASH
git reflog # 显示所有提交,包括孤立节点
git show HEAD@{5}
git show master@{yesterday} # 显示master分支昨天的状态
git log --pretty=format:'%h %s' --graph # 图示提交日志
git show HEAD~3
git show -s --pretty=raw 2be7fcb476
git stash # 暂存当前修改,将所有至为HEAD状态
git stash list # 查看所有暂存
git stash show -p stash@{0} # 参考第一次暂存
git stash apply stash@{0} # 应用第一次暂存
git grep "delete from" # 文件中搜索文本“delete from”
git grep -e '#define' --and -e SORT_DIRENT
git gc
git fsck
29.和=的区别
简单来说: == 代表相同, ===代表严格相同, 为啥这么说呢,
这么理解: 当进行双等号比较时候: 先检查两个操作数数据类型,如果相同, 则进行=比较, 如果不同, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较, 而=比较时, 如果类型不同,直接就是false.
操作数1 == 操作数2, 操作数1 === 操作数2
比较过程:
双等号==:
(1)如果两个值类型相同,再进行三个等号(===)的比较
(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
1)如果一个是null,一个是undefined,那么相等
2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
三等号===:
(1)如果类型不同,就一定不相等
(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)
(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
(4)如果两个值都是true,或是false,那么相等
(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等
(6)如果两个值都是null,或是undefined,那么相等
30.Vue组件间通信
一、props
适用场景:父子组件通信
注意事项:
如果父组件给子组件传递数据(函数):本质其实是子组件给父组件传递数据
如果父组件给子组件传递数据(非函数):本质是父组件给子组件传递数据
书写方式:3种
- [‘todos’]
- {type:Array}
- {type:Array,default:[]}
路由的props
书写形式:布尔值,对象,函数形式
二、自定义事件
适用于场景:子组件给父组件传递数据
o n 与 on与 on与emit
三、全局事件总线
$bus
使用于场景:万能
Vue.prototype.$bus=this;
四、pubsub.js
在react框架中使用较多。(消息发布与订阅)
适用于场景:万能
五、Vuex
适用于场景:万能
六、插槽
适用于场景:父子组件通信----(一般结构)
默认插槽:
具名插槽
作用域插槽
你还有什么要问我的吗
第二家公司:
自我介绍
1.实现元素居中的方法
2.跨域问题有哪些解决方法
3.uni-app打包的类型
4.简历中你进行界面设计,说说你是如何做的
5.比赛中的项目是你个人的吗
6.有看过uni-app编译成微信小程序的代码吗
7.发送网络请求,如果请求超时怎么处理
封装axios请求可以设置请求超时
8.js获取dom元素的方法
9.你的毕业设计准备做什么
10.vue双向绑定的原理
你有什么要问我的吗
引用链接:
- https://juejin.cn/post/6905539198107942919