前端面试总结(1)

6-30日面试总结

今天在手机Boss上找了两家招聘前端实习生的岗位,约了下午面试。这是从学校出来第一次进行线下面试,还是有一点点紧张的

第一家公司:东信软件

公司的具体介绍我自己也不是很了解,就不过多介绍了,直接进行面试题的总结与分析

由于是第二天总结的,有一些面试题可能想不起来了,只是把一些我印象深刻的问题总结一下

自我介绍

我自己对于自我介绍准备的是不充分的,我只简短的说了一下自己的姓名,年龄,就读学校,以及说了一下自己对前端开发感兴趣之类的,非常简短,没有条理,这一点我需要强化一下。

💩 下面是我面试时候问到的问题,我的回答很烂很烂,就不过多赘述, 直接总结答案了

1.HTML标签的分类
闭合的角度
  • 闭合标签
  • 空标签

html中大部分都是闭合标签,只有少数为空标签

常见的空标签:

<input /><img /><area /><base /><link />
位置特性
  • 块级元素
  • 行内元素
  • 行内块级元素

块级元素(block)

特点:

  1. 可以设置宽高,内、外边距
  2. 独占一行
  3. 块级元素如果不设置宽度和高度,则【宽度默认为父级元素的宽度】、【高度根据内容大小自动填充】

常见的块级元素:

div、p、h1-h6、ol、ul、li、form、table

行级元素(inline)

特点:

  1. 不可设置宽高、上下、内外边距(左右内、外边距设置有效)
  2. 宽度和高度由【内容自动填充】
  3. 其它行级元素【共处一行】

常见的行级元素:

a(锚点)、b(加粗)、i(斜体)、span(常用内联容器,定义文本内区块)、lable(input元素定义标注(标记))

行内块元素(inline-block)

特点:

  1. 可以设置宽高、内外边距
  2. 可以与其他行内元素、内联元素【共处一行】

常见的内联元素:

input、img

是否具有语义
  • 语义化标签
  • 非语义化标签

常见的语义化标签:

<header></header>  头部

<nav></nav>  导航栏

<section></section>  区块(有语义化的div)

<main></main>  主要区域

<article></article>  主要内容

<aside></aside>  侧边栏

<footer></footer>  底部
2.选择器的优先级
选择器格式优先级权重
id选择器#id100
类选择器.classname10
属性选择器a[ref=“eee”]10
伪类选择器li:last-child10
标签选择器div1
伪元素选择器li:after1
相邻兄弟选择器h1+p0
子元素选择器ul>li0
后代选择器li a0
通配符选择器*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来说

  1. 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  2. 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返回truen
some()ES5同forEach,同时回调函数返回布尔值,只要由一个为true,由some返回truen
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
CookieHTTP 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客户端优先接受加密和有身份验证的响应,支持CSPUpgrade-Insecure-Requests: 1
User-Agent用户代理User-Agent:Safari/537.36
Vary缓存策略,常用于自适应缓存配置和 SEOVary: 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服务端向客户端发送CookieSet-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)]

#体积更小

通过webpacktree-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来运行。

增加了静态类型,可以在开发人员编写脚本时检测错误,使得代码质量更好,更健壮。
优势:

  1. 杜绝手误导致的变量名写错;、
  2. 类型可以一定程度上充当文档;
  3. 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 APItree-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)

节流

防抖函数

  1. func (Function): 要防抖动的函数。
  2. [wait=0] (number): 需要延迟的毫秒数。
  3. [options=] (Object): 选项对象。
  4. [options.leading=false] (boolean): 指定在延迟开始前调用。
  5. [options.maxWait] (number): 设置 func 允许被延迟的最大值。
  6. [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);

节流函数

  1. func (Function): 要节流的函数。
  2. [wait=0] (number): 需要节流的毫秒。
  3. [options=] (Object): 选项对象。
  4. [options.leading=true] (boolean): 指定调用在节流开始前。
  5. [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与 onemit

三、全局事件总线

$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双向绑定的原理
你有什么要问我的吗

引用链接:

  1. https://juejin.cn/post/6905539198107942919
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值