前端(JS到Vue)-面试前必刷题

前端(JS到Vue)-面试前必刷题

其中参考大佬的知识点,搜索比对了很多知识,加上各种资源寻找,融合。创作为惊天八股文!

一、JS主要部分


1. JS中的数据类型(关于数据类型相关的)——

1.基本数据类型). ES5的5种Null,undefined,Boolean,Number,Stringes6新增:Symbol表示独一二的值---------示例:

javascript">   let ex***ame = Symbol('a')
        let obj = {
            [ex***ame]: 1,
            a: 2
        }
        console.log(obj, "打印对象");

* 问题(1):为什么symbol的属性名只能用 中括号 “[]” 包裹???

使用中括号包裹属性名是为了访问对象中的动态属性。 当我们使用点号(.)来访问对象属性时,我们需要明确知道属性名,并在代码中硬编码。这适用于静态属性,也就是我们在编写代码时已经知道属性名的情况。

所以说用Symbol('a')转换属性名的类型是需要过程的,属于动态属性

那么怎么调用它呢?:------

console.log(obj[Object.getOwnPropertySymbols(obj)[0]]);  // 得到结果为 1 

ES10新增:BigInt 表示任意大的整数 

要创建BigInt,只需在整数的末尾追加n即可。比较:
console.log(9007199254740995n);    // → 9007199254740995n
console.log(9007199254740995);     // → 9007199254740996
​
或者,可以调用BigInt()构造函数
BigInt("9007199254740995");    // → 9007199254740995n
​
// 注意最后一位的数字
9007199254740992 === 9007199254740993;    // → true
console.log(9999999999999999);    // → 10000000000000000

---- 重点是

-1. 它可以使Number表示不了的数字类型的值作为 Number 存入数据库。

-2. 在运算单位较大的数时克服不能精确计算的问题----

console.log(12345678901234564545789 * 2);// 结果:2.4691357802469127e+22
console.log(12345678901234564545789n * 2n);// 结果:2.4691357802469127e+22 
//或者直接调用函数 转换 BigInt(12345678901234564545789)

2.引用数据类型 Object):(本质上是由一组无序的键值对组成)

包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。

注意:类数组?

  1. 与数组类似”。
  2. 具有一个 length 属性,且 length 属性的值是一个大于等于 0 的整数。
  3. 具有索引符号([])访问方式。

(2).类数组和真实数组的区别

  1. 继承自不同的原型:数组是继承自 Array.prototype,而类数组对象则没有继承自任何原型,它只是一个普通对象。

  2. 具有不同的属性和方法:数组具有许多数组特有的方法,比如 map、reduce、filter 等等,而类数组对象则可能只拥有其中的一些方法,或者没有这些方法。

  3. 对数组方法的兼容性:由于类数组对象没有严格遵循数组的规范,因此在使用数组方法时可能会出现不兼容的情况,比如在类数组对象上调用数组的 join 方法就会返回 undefined。

  4. 可以动态扩展:数组可以动态扩展或缩小大小,而类数组对象只能通过手动操作来改变长度。

  5. 适用场景不同:数组适用于存储和操作一组具有相同类型的元素,而类数组对象适用于一些类似数组的对象,比如 DOM 的 childElementCount 属性。

2. JS中null 和 undefined 的区别?——

相同:

(1)在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等

(2) Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

不同:

(1) undefined 代表的含义是未定义,

(2) 定义了形参,没有传实参,显示undefined

(3) 一般变量声明了但还没有定义的时候会返回 undefined

(4) 对象属性名不存在时,显示undefined ------------对象属性名是什么?示例如下:

let a = { 这是自定义的一个对象
    b:1,
    c:{
      infoTitle:"信标题",
      info:"信息内容",
   }
}
当我们去用属性名调用a['f']的时候,就是一个undefined类型(注意属性名是一个字符串)

console.log(a, a['b'], "2-", a['f'],);//Object 1 '2-' undefined

(5)函数没有写返回值,即没有写return,拿到的是undefined

(6)null 代表的含义是空对象。也作为对象原型链的终点 -----------即最后  __proto__ 的值

(7)null 主要用于赋值给一些需要返回空信息的变量,作为初始化。

(8)ES10新增:BigInt 表示任意大的整数

3JS中的 栈内存—堆内存 详解和(垃圾回收机制、内存泄漏补充)

(1) 栈内存:原则为--------先进后出、后进先出访问顺序是栈顶到栈底,先投入的变量会到栈底,但使用时却是栈顶优先)重点问题:(因为栈内存中的变量是独立的,不会因为赋值新变量而导致原的变量发生改变)

let a = 20;
let b = a;
b = 30;
console.log(a);  此时a的值是多少,是30?还是20?答案是20
****** 因为栈内存中的变量是独立的,不会因为赋值新变量而导致原的变量发生改变

(2) 堆内存:无访问规律。重点问题:(因为堆内存中的变量固定,给新变量赋值堆中的某一个变量的值的时候 只是复制了一个它的指针 所以改变值的时候也会改变原 堆内存中变量的值)

let m = { a: 10, b: 20 };
let n = m;
n.a = 16;
console.log(m.a) 此时m.a的值是多少,是10?还是16?答案是16
******因为堆内存中的变量固定,给新变量赋值堆中的某一个变量的值的
时候 只是复制了一个它的指针 所以改变值的时候也会改变原 堆内存中变量的值

(3)栈内存和堆内存的优缺点总结
  1.在JS中,基本数据类型变量大小固定,并且操作简单容易,所以把它们放入栈中存储。 引用类型变量大小不固定,所以把它们分配给堆中,让他们申请空间的时候自己确定大小,这样把它们分开存储能够使得程序运行起来占用的内存最小。

  2.栈内存由于它的特点,所以它的系统效率较高。 堆内存需要分配空间和地址,还要把地址存到栈中,所以效率低。 

(4)栈(heap)内存和堆(stack)内存的垃圾回收:

  栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收。

  而堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候才会被回收。

(5)内存泄漏

内存泄漏是指我们已经无法再通过 js 代码来引用到某个对象,但垃圾回收器却认为这个对象还在被引 用,因此在回收的时候不会释放它。

**常见的内存泄露的示例:-----------如下

-1. 意外(遗漏)全局变量

-2. 定时器中含有不稳定的判断条件(若 node 元素变动则代码无效且一直执行)

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

-3. 进入死循环

-4. 不注意清除无用的定义变量代码等等。

4. JS数据类型判断(typeofinstanceofconstructorObject.prototype.toString.call() ===

typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用
console.log(typeof 2);    // number
console.log(typeof null); // object
`instanceof` 只能正确判断引用数据类型  而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型,所以这种情况下,constructor 也显得力不从心
(因为 constructor 指向创造他的构造函数,如果创造他的构造函数的原型是Number 指向被修改成了Array,那么判断出来的类型还是Number)
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
console.log((2).constructor === Number); 如果不带括号报错 标识符(字母、数字、下划线和美元符号等)或关键字(JS执行语句等)不能紧跟在数字后面 js语言规定
        如 a\1\$\_.constructor就会报错

不能紧跟在数字后面 // true  
Object.prototype.toString.call() 完美的解决方案,可以通过toString() 来获取每个对象的类型,
​
==(普通对象上是没有prototype这个属性的,需要用对象上的获取属性类型方法来判断声明的数据)==

`Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型:
​
var a = Object.prototype.toString;
 
console.log(a.call(2));[object Number]
console.log(a.call(true));[object Boolean]
console.log(a.call('str'));[object String]
console.log(a.call([]));[object Array]
console.log(a.call(function () { }));[object Function]
console.log(a.call({}));[object Object]
console.log(a.call(undefined));[object Undefined]
console.log(a.call(null));[object Null]
补充:基本数据类型赋值的时候  赋的是具体的值   引用数据类型传的是地址,一个变另一个跟着变
=== 符号判断数据类型方法不能判断数字
let a = '';
console.log(a = String);  成立
console.log(0 === Number); false 不成立

typeof null 的结果是一个object的原因 :(一个JS最初的识别问题,null的识别码为000,和object的类型一致),一般可以用 === 严格判断解决;

5、JS数据类型的转换(转数字、转字符串、转布尔值)三种情况

(1) .转数字

-1. Number   --------      强转数值
console.log(Number('6'), '打印数字字符串');数字6
console.log(Number(NaN), '打印 NaN(非数值 类型是number)');NaN
console.log(Number('汉字'), '打印纯字符');NaN
console.log(Number(undefined), '打印未定义');NaN
console.log(Number(null), '打印空');0
-2. parseInt()将字符串转化为整型
console.log(parseInt('6'), '打印数字字符串');  数字6
console.log(parseInt(NaN), '打印 NaN(非数值 类型是parseInt)');  NaN
console.log(parseInt('汉字'), '打印纯字符');  NaN
console.log(parseInt(undefined), '打印未定义');  NaN
console.log(parseInt(null), '打印空');  NaN
-3.  parseFloat()将字符串转化为整型,转化为浮点数,可以保留小数
console.log(parseFloat('6'), '打印数字字符串'); 数字6
console.log(parseFloat(NaN), '打印 NaN(非数值 类型是parseFloat())')  NaN
console.log(parseFloat('汉字'), '打印纯字符');  NaN
console.log(parseFloat(undefined), '打印未定义');  NaN
console.log(parseFloat(null), '打印空');  NaN

(2) .转字符 ---- string()toString()、隐式转换

隐式转换
let str = '123'
let res = str - 1 //122
str+1 // '1231'
+str+1 // 124
​
转换为字符串
.toString()  ⚠️注意:null,undefined不能调用
​
Number(123).toString()
//'123'
[].toString()
//''
true.toString()
//'true'
​
​
String() 都能转
String(123)
//'123'
String(true)
//'true'
String([])
//''
String(null)
//'null'
String(undefined)
//'undefined'
String({})
//'[object Object]'
​
​
隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串

(3) .转布尔值 ------ Boolean()隐式转换 -------- (0, ''(空字符串), null, undefined, NaN)会转成false

let a = 1
a+'' // '1'
转换为布尔值
0, ''(空字符串), null, undefined, NaN会转成false,其它都是true
Boolean()
Boolean('') //false
Boolean(0) //false
Boolean(1) //true
Boolean(null) //false
Boolean(undefined) //false
Boolean(NaN) //false
Boolean({}) //true
Boolean([]) //true
​
条件语句
​
let a
if(a) {
  //...   //这里a为undefined,会转为false,所以该条件语句内部不会执行
}
​
隐式转换 !!
​
let str = '111'
console.log(!!str) // true
 
 
{}和[]的valueOf和toString的返回结果?
valueOf:返回指定对象的原始值
​
对象                  返回值 
Array               返回数组对象本身。
Boolean             布尔值。
Date                存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function            函数本身。
Number              数字值。
Object              对象本身。这是默认情况。
String              字符串值。
                    Math 和 Error 对象没有 valueOf 方法。
​
toString:返回一个表示对象的字符串。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,
toString() 返回 "[object type]",其中 type 是对象的类型。
​
({}).valueOf()   //{}
({}).toString()  //'[object Object]'
[].valueOf()    //[]
[].toString()   //''

6、JS中的运算符

------   0, ''(空字符串), null, undefined, NaN)会转成false   ------



(1)&& 逻辑与    两边都是true,才返回true,否则返回false
(2)|| 逻辑或    两边只要有一个是true,就返回true,否则返回false
(3)!和 !! 逻辑非 和 非非   用来取一个布尔值相反的值  后者是在取相反的基础上在取相反
(4)? : 三元运算符 variable ? lastAble++ : lastAble--  与 if(){  }语句判断相同
(5)?.  链式运算符 let a = { // 这是自定义的一个对象
            b: 1,
            c: {
                infoTitle: "信息标题",
                info: "信息内容",
            }
        }
 ==1. console.log(a.c?.info, 1); 信息内容 1
      console.log(a.g); undefined 
      console.log(a.g?.info, 2); undefined 2
      console.log(3); 3
  --- 使用链式符判断a中是否存在g属性的时候会返回undefined ,但不影响程序运行 ---
 ==2. console.log(a.c?.info, 1); 信息内容 1
      console.log(a.g.info, 2); 此处报错:读不到 info 这个属性,停止运行
      console.log(3); 不会运行
  --- 直接调用a中的不存在属性:"g属性"中的的一个属性,程序报错,影响运行 ---

    (当调用方法时用于判断其中是否有这个属性,若为 null  或  undefined  ,程序停止运行返回  undefined  )


    补充:如果请求接口数据需要动态抓取 每条数据的不确定的深层数据,就可以判断是否存在来决定抓取,避免直接调用抛出异常

(6)?? 空值合并运算符
        let a = { // 这是自定义的一个对象
            b: 1,
            c: {
                infoTitle: "信息标题",
                info: "信息内容",
            }
        }
        let newInfo = a.c ?? '普通信息'  

   (当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返
回左侧操作数)

:有一个容易混淆的知识 ----- 运算符的优先级 || 和 && 同时使用先执行谁?如下代码:

  这种情况肯定是先计算出  flag2 && flag3 这句的结果是true还是false , 然后在拿flag1 和后面计算好的值再计算

let flag1 = true
let flag2 = true
let flag3 = true
if (flag1 || flag2 && flag3) {
    console.log(1);
}
  实际上相当于  if (flag1 || (flag2 && flag3))这样的一个结果,我们也可以自己改变
计算顺序,只要
把它变为 if ((flag1 || flag2) && flag3) 这样就行了,()的优先级最大

一般比较复杂的逻辑要用()来自己决定

7. JS相等判断的区别 (== 、=== 、Object.is())

(1) .=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之false

(2) .== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用Number()转换成Number类型在进行判断。例外规则,null==undefined,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)===Boolean(undefined)>false===false 结果为true

(3) .Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等 

==操作符的强制类型转换规则
​
字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回 false。
​
    '1' == 1 // true
    '1' === 1 // false
    NaN == NaN //false
    +0 == -0 //true
    +0 === -0 // true
    Object.is(+0,-0) //false
    Object.is(NaN,NaN) //true

8. JS - 事件

     事件的定义 :一般用户对页面发生的操作,例如,单击某个链接或按钮、在文本框中输入文本、按下键盘上的某个按键、移动鼠标等等。HTML和JavaScript两个语言之间的交互就是用JS中的(事件)作为桥梁的。

1. 事件类型

捕获与冒泡示例代码:

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<!-- js中方法需要加() 如captrueMode()  -->

<body>
    <div class="buttonBox">
        <button class="captrue" onclick="captrueMode('捕获模式')">捕获模式</button>
        <button class="bubbling" onclick="bubblingMode('冒泡模式')">冒泡模式</button>
        <div>当前模式为:(<span id="activeShow"></span>)</div>
    </div>
    <div id="six">
        6
        <div id="five">
            5
            <div id="four">
                4
                <div id="three">
                    3
                    <div id="two">
                        2
                        <div id="one">
                            1
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
<script>
    // 获取元素
    var elementOne = document.getElementById('one');
    var elementTwo = document.getElementById('two');
    var elementThree = document.getElementById('three');
    var elementFour = document.getElementById('four');
    var elementFive = document.getElementById('five');
    var elementSix = document.getElementById('six');

    const elementArr = [elementOne, elementTwo, elementThree, elementFour, elementFive, elementSix];

    var modeFlag = false; // 默认 -- 冒泡模式
    // 改变模式提示文字方法
    const updateText = (text) => {
        return document.getElementById('activeShow').innerText = text;
    }

    // ! 模式效果操作函数封装
    const directionHandle = function (event) {
        operateHandle(event, this);
    }
    // 元素添加监听控制事件方法封装
    const addEventIsElement = function () {
        elementArr.forEach(item => item.addEventListener('click', directionHandle, modeFlag));
    }
    // 元素删除监听控制事件方法封装
    const removeEventIsElement = function () {
        elementArr.forEach(item => item.removeEventListener('click', directionHandle, modeFlag));
    }
    // 切换为捕获模式
    function captrueMode(mode) {
        removeEventIsElement();
        modeFlag = true, updateText(mode);
        addEventIsElement();
    }

    // 切换为冒泡模式
    function bubblingMode(mode) {
        removeEventIsElement();
        modeFlag = false, updateText(mode);
        addEventIsElement();
    }
    bubblingMode('冒泡模式'); // 默认切换为冒泡模式  
    var showTime = 0;
    var cancelShowTime = 1000;

    // !! 封装操作函数 --  x
    const operateHandle = (event, thisDirection) => {
        if (modeFlag) {
            if (thisDirection.id === 'six') {
                showTime = 0;
                cancelShowTime = 1000;
                setTimeout(() => { thisDirection.className = 'activeColor'; }, showTime);// 2s
                setTimeout(() => { thisDirection.className = ''; }, cancelShowTime);
            } else {
                setTimeout(() => { thisDirection.className = 'activeColor'; }, showTime += 1000);
                setTimeout(() => { thisDirection.className = ''; }, cancelShowTime += 1000);
            }
        } else {
            if (event.srcElement.id === thisDirection.id) {
                showTime = 0;
                cancelShowTime = 1000;
                setTimeout(() => { thisDirection.className = 'activeColor'; }, showTime);// 2s
                setTimeout(() => { thisDirection.className = ''; }, cancelShowTime);
            } else {
                setTimeout(() => { thisDirection.className = 'activeColor'; }, showTime += 1000);
                setTimeout(() => { thisDirection.className = ''; }, cancelShowTime += 1000);
            }
        }
    }



</script>

</html>
<style lang="css">
    #six {
        width: 1200px;
        height: 300px;
        border: 1px solid #000;
        text-align: center;
        margin: auto;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -110%);
        background-color: #fff;
    }

    #five {
        width: 1000px;
        height: 250px;
        border: 1px solid #000;
        margin: auto;
        background-color: #fff;
    }

    #four {
        width: 800px;
        height: 200px;
        border: 1px solid #000;
        margin: auto;
        background-color: #fff;
    }

    #three {
        width: 600px;
        height: 150px;
        border: 1px solid #000;
        margin: auto;
        background-color: #fff;
    }

    #two {
        width: 400px;
        height: 100px;
        border: 1px solid #000;
        margin: auto;
        background-color: #fff;
    }

    #one {
        width: 200px;
        height: 50px;
        border: 1px solid #000;
        margin: auto;
        background-color: #fff;


    }

    .activeColor {
        background-color: red !important;
    }

    .captrue {
        width: 120px;
        height: 36px;
    }

    .buttonBox {
        width: 1200px;
        margin: auto;
    }

    .bubbling {
        width: 120px;
        height: 36px;
    }
</style>

(1).事件捕获

从根元素往上传递  --- ---(由外到内)

(2).事件冒泡

从元素传递到它的根源素  --- --- (由内到外)

2. 事件委托(利用事件冒泡原理,把事件委托给父元素实现,给父元素添加点击事件,可在参数e.target中获取到点击的元素,实现此行的操作)

   事件委托在需要的时候把事件交给别的元素来做 --- ---

优点 :(1) 减少内存消耗 ---- ---- 不用在每个标签上添加事件,只需获取父元素下的元素,绑定事件即可完成全部操作。

            (2) 具有动态绑定的效果 ---- ---- 在获取全部元素的条件下,不管增加或者减少的情况下都是一样的

 示例

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8" />
    <title>JavaScript</title>
</head>
 
<body>
    <ul id="list" style="width: 100px; margin: 0; float: left;">
        <li>1<button id="rowDel">删除此条</button><button id="rowEdit">修改此条</button></li>
        <li>2<button id="rowDel">删除此条</button><button id="rowEdit">修改此条</button></li>
    </ul>
    <button id="addli" onclick="addli">添加一个 li</button>
    <script>
        let list = document.getElementById("list");
        addli.onclick = function () {
            let conentElement = document.createElement("li");
            conentElement.innerHTML =
                `${ list.childElementCount + 1 }<button id="rowDel">删除此条</button><button id="rowEdit">修改此条</button>`;
            list.appendChild(conentElement);
        };
        list.onclick = function (e) {
            let activeElement = e.target;
            activeElement.id == "rowDel" ? list.removeChild(activeElement.parentNode) : activeElement
                .parentNode.innerHTML = "<div>修改中</div>"
        }
    </script>
</body>
<style>
    list {
        display: block;
    }
 
    button {
        float: right;
    }
 
    li {
        width: 300px;
        display: flex;
    }
</style>
 
</html>

3. 事件流就是:页面接受事件的先后顺序就形成了事件流。

4. 自定义事件:自定义事件,就是自己定义事件类型,自己定义事件处理函数。

9 . JS 对象(Object)、数组、字符串的方法 与 (深拷贝、浅拷贝、赋值方法

1. 对象object

`object方法`
Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
 
Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法:Object.assign(target, ...sources)
参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
返回值:修改后的目标对象。
 
 
Object.entries() ES8的Object.entries是把对象转成键值对数组, [key, value] 对的数组。
语法:Object.entries(obj)
参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
Object.fromEntries则相反,是把键值对数组转为对象
 
Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
语法:Object.values(obj)
参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
 
Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
语法:hasOwnProperty(prop)
参数:prop:要测试的属性的字符串名称或符号。
返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
 
Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
语法:Object.keys(obj)
参数:obj:要返回可枚举自身属性的对象。
返回值:表示给定对象的所有可枚举属性的字符串数组。
 
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
语法:toString()
返回值:表示对象的字符串。
 
Object.freeze()
Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
语法:Object.freeze(obj)
参数:obj:要冻结的对象。返回值:传递给函数的对象。
 
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。)
语法:const me = Object.create(person);
参数:
proto:新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性。

对象和面向对象 

对象:属性和方法的集合叫做对象(万物皆对象)。

面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。

创建对象的方式(4种):new Object、字面量、构造函数、原型。 

什么是深拷贝,浅拷贝,浅拷贝 赋值的区别,如何实现
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

1.浅拷贝:

将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存

如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,

2.深拷贝:

创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象

3、赋值:也相当于浅拷贝

 ---- \\ 深浅拷贝的实现方式 :

   1、object.assign()
   2、lodash 里面的 _.clone 
   3、...扩展运算符
   4、 Array.prototype.concat 
   5、 Array.prototype.slice
​
    深拷贝的实现方式
    1、 JSON.parse(JSON.stringify())
    2、递归操作
    3、cloneDeep
    4、Jquery.extend()   

2. 数组

1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
7、reverse( ): 原数组倒序  它的返回值是倒序之后的原数组
8、concat( ):数组合并。
9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
10、join( ):讲数组进行分割成为字符串  这能分割一层在套一层就分隔不了了
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个
19.isArray() 判断是否是数组
20. indexOf  找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
22. Array.of() 填充单个值
23. Array.from() 来源是类数组    
24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 ,    第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
find  查找这一组数 符合条件的第一个数 给他返回出来
findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来     没有返回 -1  
keys 属性名  values属性值  entries属性和属性值
forEach 循环遍历 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素
 
 
### 改变数组本身的api
1. `pop()`  尾部弹出一个元素
2. `push()` 尾部插入一个元素
3. `shift()`  头部弹出一个元素
4. `unshift()`  头部插入一个元素
5. `sort([func])` 对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前
6. `reverse()` 原位反转数组中的元素
7. `splice(pos,deleteCount,...item)`  返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items
8. `copyWithin(pos[, start[, end]])` 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝
9. `arr.fill(value[, start[, end]])` 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组
其他数组api不改变原数组
 
 
 
 
map 映射关系的数组  map 主要就是有返回值可以return 数组   判断的会返回boolean 
1、map()方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
2、map()方法按照原始数组元素顺序依次处理元素。
 
注意:
map()不会对空数组进行检测。
map()不会改变原始数组。
map() 函数的作用是对数组中的每一个元素进行处理,返回新的元素。
filter 满足条件的都能返回 是一个数组
some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false
every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false 
reduce() 累加器 把上一次计算的值,给下一次计算进行相加
set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用  
delete  [1] delete 可以删除数组中的一向
**Array.isArray()** 用于确定传递的值是否是一个 [`Array`](https://developer.mozilla.org/zh-***/docs/Web/JavaScript/Reference/Global_Objects/Array)。
flat  扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
 
 
 
//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main)   //输出:true
 
//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main)    //输出:true
 
//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
	return prev + next;
})
console.log(result);  //输出:100
 
// 4、filter  返回满足要求的数组项组成的新数组
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
    return item > 3
})
console.log(result3)  //[6,7,12,20,64,35]
 
// 5、map  返回每次函数调用的结果组成的数组
var arr4 = [1,2]
var result4 = arr4.map((item,index,arr)=>{
    return `<span>${item}</span>`
})
console.log(result4)  
/*[ '<span>1</span>',
  '<span>2</span>', ]*/
 
 
ES6数组的常用方法:
 
1、Array.from( ):将对象或字符串转成数组,注意得有length。
2、Array.of( ): 将一组值转换为数组。
3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
	target:从该位置开始替换数据;
	start:从该位置开始读取数据,默认为0;
	end:到该位置停止数据的读取,默认为数组的长度
4、find( ):用于找出第一个符合条件的数组成员。
5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
6、fill(value,start,end):使用给定值,填充一个数组。
	value:填充的值;
	start:开始填充的位置;
	end:填充结束的位置。
7、keys( ):对键名的遍历。
8、values( ):对键值的遍历。
9、entries( ):对键值对的遍历。
10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
11、flat( ):用于数组扁平,数组去除未定义。可以去除空项。
12、flatMap( ):对原数组的每个成员执行一个函数。
13、Map( ):是一组键值对的结构,具有极快的查找速度。
14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
 
 
//1、Array.from()  --   Array.of()
	var  arrayLink = {
		"0":"a",
		"1":"b",
		"2":"c",
		length:3
	}
	var arr = Array.from(arrayLink)
	console.log(arr)   // 输出: [a,b,c]
	console.log(Array.from("abcdefg"))  //输出:["a", "b", "c", "d", "e", "f", "g"]
	console.log(Array.of(1,2,3,4,5))  //输出: [1, 2, 3, 4, 5]
 
//2、copyWithin()
	var arr = [1,2,3,4,5];
	var main = arr.copyWithin(0,3);
	console.log(main);   //输出:[4,5,3,4,5]
 
//3、find()
	var arr = [1,-5,2,9,-6];
	var main = arr.find(n =>  n < 0);
	console.log(main);   //输出:-5
 
//4、fill()
	var arr = ["a","b","c","d"];
	console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"]  
 
//5、keys()  values()  entries()
	var arr = ["a","b","c","d"];
	for(let index of arr.keys()){
		console.log(index);
	}
	for(let elem of arr.values()){
		console.log(elem);
	}
	for(let [index,elem] of arr.entries()){
		console.log(index,elem);
	}  
 
//6、includes()
	let arr = [12,34,223,45,67]
	console.log(arr.includes(45))   //输出:true
	[1, 2, NaN].includes(NaN)     // true
	[1, 2, NaN].indexOf(NaN)      // -1
 
//7、Map
	var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
	m.get('Michael'); // 95
	//初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
	var m = new Map(); // 空Map
	m.set('Adam', 67); // 添加新的key-value
	m.set('Bob', 59);
	m.has('Adam'); // 是否存在key 'Adam': true
	m.get('Adam'); // 67
	m.delete('Adam'); // 删除key 'Adam'
	m.get('Adam'); // undefined
	//由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
	var m = new Map();
	m.set('Adam', 67);
	m.set('Adam', 88);
	m.get('Adam'); // 88
 
//8、Set
	//要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
	var s1 = new Set(); // 空Set
	var s2 = new Set([1, 2, 3]); // 含1, 2, 3
	//重复元素在Set中自动被过滤:
	var s = new Set([1, 2, 3, 3, '3']);
	s; // Set {1, 2, 3, "3"}  注意:数字3和字符串'3'是不同的元素
	//通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
	s.add(4);
	s; // Set {1, 2, 3, 4}
	s.add(4);
	s; // 仍然是 Set {1, 2, 3, 4}
	//通过delete(key)方法可以删除元素:
	var s = new Set([1, 2, 3]);
	s; // Set {1, 2, 3}
	s.delete(3);
	s; // Set {1, 2}

3. 字符串

1、chartAt( ):返回在指定位置的字符;
2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、toLowerCase( ):字符串转小写;
9、toUpperCase( ):字符串转大写;
10、valueOf( ):返回某个字符串对象的原始值; 
11、trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd  去除末尾空格
14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
19、replace() 把指定的字符串替换成为别的字符

4. 对象或数组去重方法

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-***patible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数组\对象去重</title>
</head>

<body>
    数组\对象的去重方法
</body>

</html>
<script lang="javascript">
    // 1.indexOf去重(单数组)
    // .(核心思想:把原数组循环遍历出来的每一项push到新的数组里,如果有相同的元素,就不会再进行push操作)
    let mobileArrAll = ['华为HUAWEI', 'VIVO', 'IQOO', '华为HUAWEI', 'OPPO', 'VIVO', '小米XIAOMI', '荣耀HONOR', '真我realme', 'IQOO', '一加Oneplus', 'SAMSUNG三星'];
    function dupArrHandle(arr) {
        var newArr = [];
        for (let i = 0; i < arr.length; i++) {
            const item = arr[i];
            if (newArr.indexOf(item) === -1) {
                newArr.push(item)
            }
        }
        return newArr;
    }
    mobileArrAll = dupArrHandle(mobileArrAll);
    console.log(mobileArrAll, 1); // 打印结果为 ["华为HUAWEI","VIVO","IQOO" ,"OPPO" ,"小米XIAOMI","荣耀HONOR" ,"真我realme","一加Oneplus", "SAMSUNG三星"]
    // 2.new Set() 去重(单数组)
    // .(核心思想:把类原数组转化为真正的数组,再进行es6的数组去重方法)
    let mobileNewSet = ['华为HUAWEI', 'VIVO', 'IQOO', '华为HUAWEI', 'OPPO', 'VIVO', '小米XIAOMI', '荣耀HONOR', '真我realme', 'IQOO', '一加Oneplus', 'SAMSUNG三星'];
    mobileNewSet = new Set(Array.from(mobileNewSet));
    console.log(mobileNewSet, 2); // 打印结果为 ["华为HUAWEI","VIVO","IQOO" ,"OPPO" ,"小米XIAOMI","荣耀HONOR" ,"真我realme","一加Oneplus", "SAMSUNG三星"]
        // 3.new Map()去重
        // (1).(单数组)
        //.(核心思想:用es6的new Map()方法,将原数组过滤,返回一个 没有找到item并且给新的数组设置一下原数组对应的item 元素 和第一个去重的原理一致)
    let mobileNewMap = ['华为HUAWEI', 'VIVO', 'IQOO', '华为HUAWEI', 'OPPO', 'VIVO', '小米XIAOMI', '荣耀HONOR', '真我realme', 'IQOO', '一加Oneplus', 'SAMSUNG三星'];
    let map = new Map();
    mobileNewMap = mobileNewMap.filter(item => !map.has(item) && map.set(item))
    console.log(mobileNewMap, 3);// 打印结果为 ["华为HUAWEI","VIVO","IQOO" ,"OPPO" ,"小米XIAOMI","荣耀HONOR" ,"真我realme","一加Oneplus", "SAMSUNG三星"]
    // (2).(数组包对象)
    let mobileNewMapObj = [{ name: '华为HUAWEI', id: 1 }, { name: 'VIVO', id: 2 }, { name: 'IQOO', id: 3 }, { name: '华为HUAWEI', id: 1 }, { name: 'OPPO', id: 5 }, { name: 'VIVO', id: 2 }, { name: '小米XIAOMI', id: 7 }, { name: '小米XIAOMI', id: 7 }, { name: '一加Oneplus', id: 8 }, { name: 'SAMSUNG三星', id: 9 }];
    let maps = new Map();
    mobileNewMapObj = mobileNewMapObj.filter((item) => !maps.has(item.id) && maps.set(item.id, item));
    console.log(mobileNewMapObj, 4);
    // 5.reduce去重(核心思想:创建一个对象,判断里面是否有数组元素的id,如果没有就往里面添加,并且往新数组里面添加一条数据,反之跳过添加)
    let mobileNewReduce = [{ name: '华为HUAWEI', id: 1 }, { name: 'VIVO', id: 2 }, { name: 'IQOO', id: 3 }, { name: '华为HUAWEI', id: 1 }, { name: 'OPPO', id: 5 }, { name: 'VIVO', id: 2 }, { name: '小米XIAOMI', id: 7 }, { name: '小米XIAOMI', id: 7 }, { name: '一加Oneplus', id: 8 }, { name: 'SAMSUNG三星', id: 9 }];
    let obj = {};
    const mobileNewReduces = mobileNewReduce.reduce((lastArr, item, index, primaryArr) => {
        obj[item.id] ? '' : obj[item.id] = item.id && lastArr.push(item);
        return lastArr
    }, initLastArr = [])
    console.log(mobileNewReduces, 5);
</script>

---------- 10 . JS 函数 

1. 函数声明的方式

1 .普通声明
    function caseHandle(params) {
        return params ? { 'A': params, } : { 'A': '1', }
    }
    console.log(caseHandle('2'));

2 .函数表达式
    const caseHandle = function (params) {
        return params ? { 'A': params, } : { 'A': '1', }
    }
    console.log(caseHandle('2'));
3 .数据声明
    const userInfo = {
        name: '张三',
        age: 18,
        gender: '男',
        hobby: ['篮球', '足球', '游泳'],
        handleChange: function (params) {
            return params ?? this.hobby
        }
    }
    console.log(userInfo.handleChange());
4. 箭头函数
   ( 最大的特点也就是 :this可以访问父级上下文,在闭包中可以访问到内部变量
    缺点也就是没有arguments参数,和不能使用new操作符构造 )
    const caseHandle = ()=> {
        return params ? { 'A': params, } : { 'A': '1', }
    }
    console.log(caseHandle('2'));
5. 自执行函数 立即执行函数可用于封装一些临时或局部变量
    const autoFun = function () { alert('函数立即执行了') }()
6. 构造函数声明
    function Person(name, age) {
    this.name = name;
    this.age = age;
  } 
    var person = new Person("John", 30);
    var person2 = new Person("John", 30);
    console.log(person.name); // 输出:John
    console.log(person2.age); // 输出:30
7. 函数覆盖 
    function fun4() {
        return 'hello'
  }

    function fun4() {
        return 'hello world'
  }

    console.log(fun4())   // hello world
    console.log(fun4(2, 3))  // hello world

    这个就有意思了 比如组件中哪个自带的方法不可以改变,我们就可以覆盖掉原本组件里的函数不过这个做           法是比较危险的,这是element-vue2中走马灯的去掉鼠标移入方法
    delHandleMouseEnter() { this.$refs.carousel.handleMouseEnter = () => {}; },
7. class 类声明
( 用于定义对象对象模板,可轻松实例化多个具有相同结构的对象,实现的对象的复用性 )
  class Person {
     constructor(name, age) {
     this.name = name;
     this.age = age;
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person("John", 30);
person.sayHello(); // 输出:Hello, my name is John

2. 函数的长度 (参有默认值将不会计算长度)

function fun1(a) { }
function fun2(a, b) { }
function fun3(a, b, c) { }
function fun4(a, b, c, d) { }
function fun5(...args) { }
function fun6(a = 1, b, c, d) { }
​
console.log(fun1.length) // 1
console.log(fun2.length) // 2
console.log(fun3.length) // 3
console.log(fun4.length) // 4
console.log(fun5.length) // 0
console.log(fun6.length) // 0

3. arguments 详解

arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。

取之可以用 展开运算符来 数组和类数组类数组: ①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法; ③类数组是一个普通对象,而真实的数组是Array类型。

常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"))

4. this指向的问题(高频) 

在全局的环境下this是指向window 的

普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window

在对象里调用的this,指向调用函数的那个对象,

在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象

方法中的this谁调用就指向谁。

箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this

改变this的指向 .call()、.apply()、.bind()()

-  call  和  apply  方法立即执行函数,并传递参数,区别在于参数的传递方式。 
-  bind  方法创建一个新的函数,需要手动调用该函数才会执行。


1. -  call  和  apply  方法
var obj = {
  name: "John",
  greet: function(message) {
    console.log(message + " " + this.name);
  }
};

var obj2 = {
  name: "Jane"
};

// 使用 call 方法
obj.greet.call(obj2, "Hello"); // 输出:Hello Jane

// 使用 apply 方法
obj.greet.apply(obj2, ["Hi"]); // 输出:Hi Jane


2. bind 方法

// 使用 bind 方法
var greetFunc = obj.greet.bind(obj2);
greetFunc("Hey"); // 输出:Hey Jane

bind 返回的函数可以作为构造函数 但 如果是直接调用,f1()和f2()的this指针指将会不同.

        let obj = { a: 1 }
        function f1() {
            console.log(432, this) // 432 f1 {}
        }
    1.
        const f2 = f1.bind(obj)
        const obj2 = new f2()
        obj2.a = 223
        console.log(obj2) // f1 {a: 223}
         ----------------

    2.
        const f2 = f1.bind(obj)()
        const obj2 = new f2() 指针不同有可能会造成阻塞报错

5. 闭包原理 

闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
​
    1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
      如果已存在“闭包”,则只需要增加对应属性值即可。
    2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
      所以内部函数可以继续使用“外部函数”中的变量
​
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

实现代码示例:

1. 闭包示例(在调用外部函数之后,把内部函数反出)
    function makeAdder(x) { 
      return function(y) {
       return x + y;
      };
    }

    var adder = makeAdder(5);
    console.log(adder(10)); // 15

2. 递归示例 (自己调用自己)
    function factorial(n) {                             
        if (n === 0) {
            return 1;
        } else {
            return n * factorial(n - 1);
        }
    }

    for (var i = 10; i >= 1; i--) {
        console.log(factorial(i));
    }
   打印结果 120 24 6 2 1

6. 函数柯里化(卡瑞化、加里化)?

 概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。

特点:

①接收单一参数,将更多的参数通过回调函数来搞定;

②返回一个新函数,用于处理所有的想要传入的参数;

③需要利用call/apply与arguments对象收集参数;

④返回的这个函数正是用来处理收集起来的参数。

作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。

用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。 

 柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。

我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数

// 简单的相加函数
var add = function (x,y) {
    return x + y
}
// 调用:
add(1,2)
​
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
    return function (y) {
        return x + y
    }
}
add(1)(2)

7. JS高阶函数 

(1). 上面所说的闭包其实就是一种(函数作为返回值返回)

(2). 函数作为参数传递 (也就是回调函数)

function doSomething(callback) {
  // 执行一些操作
  // 调用回调函数
  callback();
}

function callbackFunction() {
  console.log("回调函数被调用");
}

doSomething(callbackFunction); // 输出:回调函数被调用

11. 构造函数 与 ES6新特性Class类

1.  构造函数:

        - 构造函数是一种传统的创建对象的方式,使用function关键字定义

        - 构造函数中通过this关键字定义对象的属性和方法

        - 构造函数可以通过new关键字实例化对象

2. ES6的Class类

        - class是ES6引入的新特性,是一种更现代化的创建对象的方式。

        - class关键字定义类,类中包含构造函数constructor和类方法。

        - 使用class语法更清晰和简洁,更符合面向对象编程的思想。

class类拥有强大的方法与功能

1. new操作符的原理

new实际上是在堆内存中开辟一个空间。
    ①创建一个空对象,构造函数中的this指向这个空对象;
    ②这个新对象被执行[ [ 原型 ] ]连接;
    ③执行构造函数方法,属性和方法被添加到this引用的对象中;
    ④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
​
    1. 自己定义一个new方法
        function myNew(constructor, ...args) {
            // 创建一个新的空对象,继承自构造函数的原型对象
            const obj = Object.create(constructor.prototype);

            // 将构造函数的作用域赋给新对象(即设置 this 指向新对象)
            const result = constructor.apply(obj, args);

            // 如果构造函数返回了一个对象,则返回该对象;否则返回新创建的对象
            return (typeof result === 'object' && result !== null) ? result : obj;
        }
    2. 调用myNew方法
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }

        Person.prototype.greet = function () {
            console.log("Hello, my name is " + this.name);
        };

        var john = myNew(Person, "John", 25);
        john.greet(); // 输出:Hello, my name is John

2. Class类 (更现代化和推荐的创建对象的方式,而构造函数仍然是JavaScript中传统的创建对象的方法。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log("Hello, my name is " + this.name);
  }
}

const person1 = new Person("Bob", 25);
person1.greet();

 ----------  12. JS 运行机制

 1. (堆栈存储、垃圾回收与内存泄漏 在以上内容已经有了、作用域等等,访问如下:

-----------------------------------------------------------------------------------------------------------------------------------------------------------

     (1). 堆栈存储、垃圾回收与内存泄漏 --- (前面第3标题)

-----------------------------------------------------------------------------------------------------------------------------------------------------------

     (2). JS 的 作用域作用域链自由变量变量提升---(预解析)
- 1. 作用域 is what? ---  其中包含(全局作用域和局部作用域

解:JavaScript的作用域是指变量在代码中可访问的范围

  (1). 全局作用域 : 整个代码中都可以访问的变量。

  (2). 局部作用域 : 特定代码块中声明的变量,包括函数作用域块级作用域(通常是由花括号{}包围的代码使用let和const关键字可以创建。

 - 2. 作用域链 is what?(作用域机制)

  访问一个变量时,JavaScript引擎会按照作用域的层次结构从内到外查找该变量。如果在当前作用域找不到该变量,它会继续向上一级作用域查找,直到找到该变量或达到全局作用域为止。

 - 3. 自由变量 is what(作用域机制)向上查找的这种机制诞生了 名词“作用域链”

  在函数内部声明的变量,但在函数外部被引用。自由变量在函数内部是不可见的,因此必须在函数外部声明。(如果在函数内部修改了自由变量,那么在函数外部也会看到变量的修改。很容易使用时变量早已发生改变,导致错误

 - 4. 变量提升 is what?(变量的特性 )---(var可提升 let和const不可提升

过程

1.在解析阶段 JS会检查语法,并对函数进行预编译。

2.在执行阶段,就是按照代码的顺序依次执行。

优点:

  1. 提高性能

  2. 容错性更好 

缺点:忽略变量

function foo() {
  var x = 10;
  console.log(x);
}

foo();

  这个结果是可以得到 x 的值的,因为var有变量提升的这个机制,如果没有变量提升的这个机制,就会报错,(准确来说var是一个全局作用域,正因为他有了变量提升,所以也能再内部访问到,如果没有,则在函数内部找不到块级变量,在全局也找不到变量,就会报错没有声明)

2. 谈谈JS的运行机制---------(单线程运行——即 同一时间只能做同一件事情)

js代码执行过程中会有很多任务,这些任务总的分成两类:

  • 同步任务

  • 异步任务

需要注意的是除了同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务

微任务包括了 promise 的回调、node 中的 process.nextTick async/await
​
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate,render 一类的定时事件,还有如 I/O 操作、UI 渲染。、对 Dom 变化监听的 MutationObserver。等 

线程,进程?
线程是最小的执行单元,进程是最小的资源管理单元一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程

1. 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。

2. 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务

3. 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。

4. 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。

5. 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。 

    mounted() {                         created() {                                                                                                                                                                                                                           
        setTimeout(function () {             setTimeout(function () {                                                                                  
            console.log(1)                           console.log(1)                                                                            
        }, 0);                               }, 0);                                                                      
        new Promise(function (resolve) {        new Promise(function (resolve) {                                                                                          
            console.log(2);                          console.log(2);                                                                             
            resolve()                                resolve()                                                                       
        }).then(function () {                   }).then(function () {                                                                               
            console.log(3)                           console.log(3)                                                                            
        });                                     });                                                                      
        this.$nextTick(function () {                 this.$nextTick(function () {                                                                                      
            console.log(4)                           console.log(4)                                                                            
        })                                      })                                                                      
        console.log(5)                               console.log(5)                                                                        
    },                                  ······},
    输出结果:‘2-5-4-3-1’                输出结果:‘2-5-3-4-1’

        第一轮:主线程开始执行,遇到setTimeout,将setTimeout的回调函数丢到宏任务队列中,在往下执行new Promise(Promise本身是同步的只不过then,catch回调方法是异步)立即执行,输出2,then的回调函数丢到微任务队列中,再继续执行,遇到vue.nextTick,同样将回调函数扔到微任务队列,再继续执行,输出5,当所有同步任务执行完成后看有没有可以执行的微任务,发现有then函数和nextTick两个微任务,先执行哪个呢?vue.nextTick是DOM更新完成之后的回调函数,因此先执行vue.nextTick输出4然后执行then函数输出3,第一轮执行结束。

        第二轮:从宏任务队列开始,发现setTimeout回调,输出1执行完毕,因此结果是25431 (这个顺序是在mounted中得出的,因为此时DOM早已更新完毕,所以他是在微任务中第一个执行的)

        created:在created中Dom尚未渲染完毕所以会在promise.then后面执行

一般情况nextTick都是在promise.then后面执行


---------- 13. SSR (传统服务端渲染) VS CSR(客户端渲染)     

  • 一、 SSR(传统服务端渲染)
  1. 传统的服务端渲染有 asp(基于 do*** 即.*** 渲染) jsp(基于 java 渲染) ejs(基于 node.js 渲染) 三种 --->服务端语言在通过这些模板引擎将数据和 dom 在服务端渲染,返回一个完整的静态页面给客户端直接渲染。

  2. 优点:
    (1) 首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件;
    (2) SEO(搜索引擎)优化:搜索引擎爬虫可以直接抓取到完整HTML页面的内容,不需要通过JavaScript解析
    (3) 可以生成缓存片段、节能; 

  3. 渲染原理或过程
    (1) 客户端发给服务端一个 http 请求。
    (2) 服务端响应 http 请求,返回一个 html 字符串给客户端。
    (3) 客户端渲染 html

  4. 缺点
    (1) 前后端不分离,开发者要求前后端一起开发
    (2) 数据刷新需要重新加载页面
    (3) 服务端压力大

  5. 案例爱彼迎 | airbnb.***

  • 二、 CSR(客户端渲染)
  1. 客户端渲染
    代表技术栈 Vue React Angular 等 SPA(单页面应用程序) 构建程序项目。即给客户端一个空的 html 页面, (客户端)然后通过 JS 渲染页面和进行路由的跳转,数据通过 Ajax 请求成功,再进行页面的拼接和展示
  2. 渲染原理或过程
    (1) 客户端发给服务端一个 http 请求。
    (2) 服务端响应请求返回一个空的 html 页面
    (3) 客户端加载必须的 JS 文件,请求接口
    (4) 将生成的 dom 插入 html 页面中
  3. 优点:

        (1) 前后端分离,开发效率高。
        (2) 用户体验更好,我们将网站做成SPA(单页面应用)或者部分内容做成SPA,当用户点击时,不会形成频繁的跳转。
        缺点:
        (3) 前端响应速度慢,特别是首屏。
        (4) 不利于SEO

  1. 缺点
    (1) 首屏加载慢(需要 js 文件加载、下载解析之后才能渲染,所以会产生白屏)
    (2)不利于 SEO (SEO 是(Search Engine Optimization)的缩写,意思是 搜索引擎优化。目的是让搜索引擎的爬虫更容易收集到你网站的内容)
  2. 代码层面理解
    案例 飞书

    (1) 没有 www 文件作为渲染
    (2) 用 vendor 文件作为打包好的文件

    (3) 在源码中用一个 id 把页面注入到一个 div 中,python 爬虫是爬不到的
  • 三、 同构(现代服务端渲染)
  1. 现代服务端渲染
    即应用程序大部分代码在服务端(node 服务端)和客户端上渲染:同构

  2. 原理
    (1) 客户端发起 http 请求
    (2) 服务端把 Vue 实例渲染成静态内容发送给客户端
    (3) 然后在客户端把事件、响应式特性绑定

  3. 缺点
    (1) 服务器压力大
    (2) 构建设置和部署需要更多要求
    (3) 一些第三方库需要特殊处理

  4. 优点
    (1) 不需要等待首屏加载(不会出现空白页面)
    (2) 前后端分离
    (3) 利于 SEO
    (4) 利用框架 Nuxt.js Next.js 等

  5. 案例 美团

---------- 14. JS的DOM(文档对象模型)和BOM(浏览器对象模型)

1. DOM——文档对象模型

就是我们常说的dom树,通过一个根元素创造许多子元素,通过document.getElementById('ID')等等方法可以获取到元素

拿到指定节点
var id = document.getElementById("id");  //返回带有指定id的元素
var name = document.getElementByTagName("li"); //返回带有指定标签的元素
var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
 创建DOM节点
var node = document.createElement("div");
var attr = document.createAttribute("class");
var text = document.createTextNode("菜呀菜");`
 插入DOM节点
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在node元素内child前加入新元素`
 删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
 修改DOM节点
node.setAttribute("class","name") //修改设置属性节点
node.replaceChild(pre,child)  //父节点内新子节点替换旧子节点`
 常用DOM属性
node.innerHtml  //获取/替换元素内容
node.parentNode  //元素节点的父节点
node.parentElement  //元素节点的父元素节点(一般与Node节点相同)
node.firstChild  //属性的第一个节点
node.lastChild   //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
node.childNodes  //元素节点的子节点(空格,换行默认为文本节点)
node.children    //返回当前元素的所有元素节点
node.nodeValue   //获取节点值
node.nodeName    //获取节点名字
node.attributes  //元素节点的属性节点
node.getAttribute("name")  //元素节点的某个属性节点
node.style.width = "200px"  //设置css样式`

常用的api offset、client、scroll的用法?
offset系列 经常用于获得元素位置 offsetLeft offsetTop

client经常用于获取元素大小 clientWidth clientHeight 

scroll 经常用于获取滚动距离 scrollTop scrollLeft

2. 回流与重绘

(1).重绘

效果:页面会有细微改动,不会刷新

当DOM的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘( repaint)。

(2).回流

效果:重新刷新页面

  • 页面首页渲染
  • 浏览器窗口大小发生变化(读写offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作)
  • 内容变换(使DOM节点发生增减或者移动)
  • 添加或者删除节点
  • 激活CSS伪类

(3).优化思路 

1.多个属性尽量使用简写,例如:boder可以代替boder-width、boder-color、boder-style
2.创建多个dom节点时,使用documentfragment创建
3.避免使用table布局
4.避免设置多层内联样式,避免节点层级过多
5.避免使用css表达式
6.将频繁重绘或回流的节点设置为图层,图层能够阻止该节点的渲染行为影响到别的节点(例:will-change\video\iframe等标签),浏览器会自动将该节点变为图层 

3. 同源策略 是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 

 15. JS 本地存储

以下是对JavaScript中cookies、sessionStorage和localStorage的简单比较:

  1. Cookies是由服务器发送到浏览器并存储在本地的数据,每次发送HTTP请求时都会携带cookies,因此可以用于跟踪用户的会话状态。而sessionStorage和localStorage是由浏览器本地存储的数据,不会随着HTTP请求发送。

  2. Cookies的大小限制为4KB左右,而sessionStorage和localStorage的大小限制通常为5MB或更大。

  3. Cookies可以设置过期时间,而sessionStorage和localStorage则不会过期,除非手动清除。

  4. Cookies可以设置HttpOnly属性,防止XSS攻击,而sessionStorage和localStorage则没有这个属性。

  5. Cookies始终在同源的HTTP请求中携带,而sessionStorage和localStorage则只在同一页面中共享。

sessionStorage与localStorage比较

        sessionStorage:

(1)存储时间:只能保留在当前会话期间,当前页签关闭,则存储的数据就会清除

(2)数据共享问题:只能在同一浏览器页签中共享,不同则不共享

(3)一般用于存储临时会话,如用户登录状态,表单数据等。用完之后不必再存储

        localStorage:

(1)存储时间:数据一直保留,除非通过代码或从浏览器手动删除

(2)数据共享问题:在同一浏览器中,不同页签都可共享

(3)用于长久存储,如记录用户设置偏好,缓存数据等

// 设置cookie
document.cookie = "username=John Doe; expires=Thu, 18 Dec 2022 12:00:00 UTC; path=/";

// 设置sessionStorage
sessionStorage.setItem("username", "John Doe");

// 设置localStorage
localStorage.setItem("username", "John Doe");

// 获取cookie
let cookieValue = document.cookie.replace(/(?:(?:^|.*;\s*)username\s*\=\s*([^;]*).*$)|^.*$/, "$1");
console.log(cookieValue);

// 获取sessionStorage
let sessionValue = sessionStorage.getItem("username");
console.log(sessionValue);

// 获取localStorage
let localValue = localStorage.getItem("username");
console.log(localValue);

(2). 设置Cookies失效时间

name cookie的名字(键)
value cookie存放的值
expires:指cookie过期的时间(该时间为世界时间  UTC时间)也称为格林威治时间
max-age:指cookie最大的存活有效期(单位:秒)
domain: 指的是cookie当前的域
path:指cookie当前的路径
size:指cookie存放的大小
secure:指cooke的安全属性

注意:expires和max-age的默认值为session代表关闭浏览器,该cookie则清空、失效



要在JavaScript模块中使用此代码,您可以创建一个新的Cookie对象并设置expires或max-age属性。例如,以下代码创建一个一年后到期的cookie:

const cookie=新cookie(“名称”、“值”);

cookie.expires=新日期(Date.now()+3600*24*365);

document.cookie=cookie.toString();

您也可以使用setCookie()方法来设置cookie。例如,以下代码创建一个一年后到期的cookie:

document.setCookie(“name”,“value”,{expires:new Date(Date.now()+3600*24*365)});

请注意,并非所有浏览器都支持setCookie()方法。有关更多信息,请参阅[MDN文档](https://developer.mozilla.org/en-us/docs/web/api/document/cookie)。

16 .JS 原型与原型链

1. prototype(显示原型)

        每个函数都有一个prototype属性,被称为显示原型

2. _ _proto_ _(隐式原型

        每个实例对象都会有_ _proto_ _属性,其被称为隐式原型,指向自身构造函数的class类上的显式原型prototype

3. constructor

        每个prototype原型(包括指向prototype的__proto__)下都有一个constructor属性,指向它关联的构造函数。可以接受构造函数传递过来的参数

4. 原型链

获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype)为止。Object.prototype对象也有__proto__属性值为null。

其实就是 每个函数上都有一个(prototype), (prototype)里面有一个(__proto__),这个就是他上层 class类 的(prototype),class类的(prototype)中也有个(__proto__),就是Object上的prototype,然而(Object上的prototype)中的(__proto__)就是原型最顶端 值为null

17. JS 防抖、节流 性能优化

防抖:用户操作事件,到规定时间触发操作,用户连续操作,在当前操作距上一次操作不足规定的时间,则上一次操作无效,从当前操作开始计时,到时触发操作(当然我们可以改变一下函数让第一次立即触发)

节流:用户操作事件,立即触发,用户连续操作,在上一次操作生效不足规定的时间内,则操作无效,上一次生效的操作时间已到,则触发立即生效

(1). 按钮点击事件:防止按钮被重复点击,例如提交表单、发送请求等操作,只执行最后一次点击的操作。 
 
(2). 输入框实时搜索:在用户输入内容时,使用防抖来减少实时搜索请求的频率,减轻服务器压力。 


(3). 页面滚动事件:当用户滚动页面时,使用节流来控制触发滚动事件的频率,避免过多的计算和操作。 
 
(4).
窗口大小调整事件:当窗口大小调整时,使用节流来限制触发调整事件的频率,提高性能。 
 
(5).
鼠标移动事件:在鼠标移动过程中,使用节流来控制触发事件的频率,避免过多的计算和操作。 
 
(6).
滚动加载:在滚动加载数据的场景中,使用节流来限制请求的频率,避免一次性请求过多的数据。 
 
(7). 实时定位:在实时定位应用中,使用节流来限制定位请求的频率,减少资源消耗。 
 
这些应用场景都是为了优化性能,避免不必要的计算和操作,提升用户体验。根据具体的需求和场景,选择合适的防抖或节流策略可以有效地控制事件触发的频率。 

防抖    
    function debounce(callback, delay, immediate) {
            let timeoutId;

            return function () {
                const context = this;
                const args = arguments;

                const later = function () {
                    timeoutId = null;
                    if (!immediate) {
                        callback.apply(context, args);// 函数在调用的时候改变指针,不能在外面改变指针,否则会调用两次,谨慎使用
                    }
                };
                const callNow = immediate && !timeoutId;


                if (callNow) {
                    callback.apply(context, args);
                }
                clearTimeout(timeoutId);
                timeoutId = setTimeout(later, delay);
            };
        }
    // 操作函数
    const operateFun = debounce(function (params) {
        console.log(params);
    }, 1000, false) 


节流
    function throttle(func, wait, options) {
        var previous = 0;
        var context = this;
        var args = arguments;

        function throttled() {
            var now = Date.now();
            if (now - previous >= wait) {
                previous = now;
                func.apply(context, args);
            }
        }

        return throttled;
    }
    const operateFun = throttle(function (params) {
        console.log('111');
    }, 1000) 

18. JS各大循环示例

  1. for循环:for循环是最常用的循环语句之一,它允许你指定循环的初始条件、循环条件和循环后的操作。语法如下:
for (初始条件; 循环条件; 循环后的操作) { // 循环体代码 }

示例:


for (var i = 0; i < 5; i++) { console.log(i); // 输出:0, 1, 2, 3, 4 }
  1. while循环:while循环在循环开始之前先检查循环条件,只有当条件为真时才执行循环体。语法如下:
while (循环条件) { // 循环体代码 }

示例:


var i = 0; while (i < 5) { console.log(i); // 输出:0, 1, 2, 3, 4 i++; }
  1. do…while循环:do…while循环与while循环类似,但它会先执行一次循环体,然后再检查循环条件。语法如下:
do { // 循环体代码 } while (循环条件);

示例:


var i = 0; do { console.log(i); // 输出:0, 1, 2, 3, 4 i++; } while (i < 5);
  1. for-in遍历key,for-of遍历下标
  var array = [1, 2, 3]
        var arrayObj = [{
            a: 1,
            b: 2,
            c: 3
        }]
        var object = {
            a: 1,
            b: 2,
            c: 3
        };
        // key
        for (const key in array) {
            if (Object.hasOwnProperty.call(array, key)) {
                console.log(key); // 0 1 2
            }
        }
        for (const key in arrayObj) {
            if (Object.hasOwnProperty.call(arrayObj, key)) {
                console.log(key); // 0
            }
        }
        for (const key in object) {
            if (Object.hasOwnProperty.call(object, key)) {
                console.log(key); // a b c
            }
        }
        // 下标
        for (const key of array) {
            console.log(key); // 1 2 3
        }
        for (const key of arrayObj) {
            console.log(key); // {a: 1, b: 2, c: 3}
        }
        for (const key of object) {
            console.log(key); // 对象不可遍历
        }

19. 一些封装的方法

    
1. 删除分页重新计算当前页码

const setPageIndex = function (selected, total, pageSize, pageIndex) {
        const len = typeof selected === Number ? selected : selected.length;
        (当前总数 - 选中条数) / 每页条数 = 当前页数
        当前页数 < 之前页数 则返回第一页 否则返回之前之前页数
        4.6 < 5  这个 4.6 相当于4页半 也是5页 所以需要 向上取整
        if (Math.ceil((total - len) / pageSize) < pageIndex) {
            return 1;
        }
        return pageIndex;
    };

二 、ES6主要部分


1. ES6的新特性

  1. 新增了块级作用域(let,const)
  2. 提供了定义类的语法糖(class)
  3. 新增了一种基本数据类型(Symbol)
  4. 新增了变量的解构赋值
  5. 函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
  6. 数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法
  7. 对象和数组新增了扩展运算符
  8. ES6 新增了模块化(import/export)
  9. ES6 新增了 Set 和 Map 数据结构
  10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
  11. ES6 新增了生成器(Generator)和遍历器(Iterator)

 2. require与import的区别和使用

1 .require

**require 语法** require 语法是 ES6 之前的 JavaScript 中加载模块的标准方式。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。使用 require.resolve() 函数来解析模块,解析方式是全局的,这意味着它会在所有模块中查找模块。 

 2. import

1、import是ES6中的新语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块export可以有多个,export default只能有一个。使用 import.meta.resolve() 函数来解析模块。解析方式是局部的。

区别 

1. require是***monJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;

2. require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;

3. require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码

4. require用解析方式是全局的且运行时才引入模块的属性所以性能相对较低import解析方式是局部的且编译时引入模块的属性所所以性能稍高。 

3. 箭头函数 

js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数。

1、 箭头函数是匿名函数不能作为构造函数,不能使用new

2、 箭头函数没有arguments,this继承自外层第一个普通函数的this

3、 箭头函数没有prototype(原型),所以箭头函数本身没有this

4、 箭头函数不能当做Generator函数,不能使用yield关键字、

5、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁

6、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。 

4.  Es6的Promise和Es7的async/await

1. Promise (Promise 是 JavaScript 的一个异步编程模型,它使用了微任务队列来实现异步操作

 (1)Promise的三种状态:pending(进行中)、fulfilled(已完成)和 rejected(已失败)

 (2)Promise的回调:then(成功回调)、catch(失败回调)和 finally(操作完成回调)

 (3)Promise的方法 all()、reject()、resolve()、race()

  new Promise((resolve, reject) => {
            处理请求
            axios.get('../../request').then((result) => {
                resolve(result) resolve 解析并返回请求成功的参数
            }).catch((err) => {
                reject() 返回错误信息
            })
        }).then(() => {
            成功回调
        }).catch(() => {
            失败回调
        }).finally(() => {
            完成回调
        })

 Promise的方法

1. all() 执行多个promise
  
    let p1 = new Promise((resolve, reject) => { });
    let p2 = new Promise((resolve, reject) => { });
    let p3 = new Promise((resolve, reject) => { });


    使用Promise.all 其中id为69的商品, 返回失败, 会导致整个Promise接受到reject状态.
    所以进行改造, p catch 得到的err  为返回失败抛出的信息, 进行置空

    Promise.all([p1, p3, p2].map(p => p.catch(e => '')))
        .then((result) => {
            console.log(result); 结果为:['p2', 'p3', 'p1'] 同步且倒序执行
        })
        .catch((error) => { console.log(error); });

2. race() 返回第一个执行完毕的promise
    
    Promise.race([p1, p2, p3, p4]).then(value => console.log('value:', value)).catch(err => console.log('err:', err))	// 延时由请求B决定,1s后输出结果

3. resolve() 将现有对象转为 Promise 对象。并返回 Promise.reject() 效果类似

    Promise.resolve('ok')
	// 等价于
	new Promise(resolve => resolve('ok'))

    
    const obj = {
		then(resolve) {
			console.log('我是一个then方法')
			resolve('ok')
		}
	}
	Promise.resolve(obj).then(value => console.log('value:', value))
	// 我是一个then方法
	// value: ok

(简单来说 当我们要判断 是否具有执行条件的时候 可以用Promise.resolve()方法优化代码 他会等条件通过的时候执行 就不用我们写else 方法了)
 2. Es7 async/await

Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用(是promise的进一步优化)

5. 解构赋值 

1. 提取变量
const person = {
  name: "John Doe",
  age: 20,
  gender: "male",
};

const { name, age, gender } = person;

console.log(name); // John Doe
console.log(age); // 20
console.log(gender); // male

2. 赋值 或 提取剩余变量

const arr = [1, 2, 3,4,5,6];

const [a, b, ...c] = arr;

console.log(a); // 1
console.log(b); // 2
console.log(c); // 剩余的变量

3. 变量交换 
 let a = 'html'
 let b = 'css'
 [a,b] = [b,a]
 //a:css  b:html


6. for...in 迭代和 for...of 有什么区别

1. 普通数组(效果一样,直接遍历值 )

2. 对象

for...in 遍历键值 

for...of 不能直接遍历对象,可以用Object.keys()把键值转为数组再去遍历

3. 数组包对象

for...in 遍历索引

for...of 遍历索引相对的对象,可以直接 iterator.key 得到值

(1)注:for...in 不可遍历 symbol 属性 可以遍历到公有中可枚举的

(2)注:for...in遍历 为什么要使用 hasOwnProperty 检查属性 (遍历范围包括它的原型链确保当前属性是否是对象自身的属性,而不是从原型链中继承的属性。

7.  generator 简述 

generator 函数也是es6提出的异步编程的解决方案 需要注意的是 与promiose、async\await语法有很大的差别 更像是一个状态机 (可以控制代码的执行状态 ),不仅可以用于解决异步的方法,也可以用于在对象迭代、控制输出上 功能还是很强大的

1.  在普通函数 function 的后面跟一个星号 “*” ,作为  function* name(){} 的形式声明函数

2. 在函数内部可以使用 yield 语法 暂停 函数的执行,在外部调用函数上的 .next()方法 开启函数的执行

 利用generator 的示例


   let num = function (count) {
        console.log(`剩余${count}次`);
    }

    let res = function* (count) {
        debugger
        while (count > 0) {
            count--
            yield num(count)
        }
    }
    let start = res(5)
    console.log(start.next());  剩余 1 次
    console.log(start.next());  剩余 2 次
    console.log(start.next());  剩余 3 次
    console.log(start.next());  剩余 4 次
    console.log(start.next());  剩余 5 次
    console.log(start.next());  done 值为true    

8. js构造函数的静态成员和实例成员

实例成员: 构造函数中this上添加的成员, 只能由实例化的对象来访问
静态成员:构造函数本身上添加的成员, 只能由构造函数本身来访问 

    function Person(left) {
        this.left = left
        // 这些是 实例成员
    }
    Person.a = 1 // 静态成员

    Person.say = function () { // 静态成员
        console.log('say666');
    }


    Person.prototype.center = 'center' // 实例成员

    Person.prototype.sayHello = function () { // 实例成员
        console.log('sayHello');
    }

    let person1 = new Person('左');
    // 因为
    console.log(person1.__proto__ == Person.prototype); // ture

    console.log(Person.left);// undefined
    console.log(person1.left);// 左
    console.log(Person.a);// 1
    console.log(person1.a);// undefined
    console.log(Person.center);// undefined
    console.log(person1.center);// center 
    Person.sayHello() // -is not a function
    person1.sayHello() // sayHello

构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?

  1. 新建了一个Object对象

  2. 修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数

  3. 为Object对象添加了一个proto属性,是其指向构造函数的prototype属性

  4. 将这个Object对象返回出去

9、set和map数据结构有哪些常用的属性和方法?

set数据的特点是数据是唯一的

const set1 = new Set()
 
增加元素 使用 add
set2.add(4)
 
是否含有某个元素 使用 has
console.log(set2.has(2)) 
 
查看长度 使用 size
console.log(set2.size) 
 
删除元素 使用 delete
set2.delete(2)
 
size: 返回Set实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。

不重复性

    const set2 = new Set([1, 2, '123', 3, 3, '123'])
    console.log(set2);
    
    // Set`的不重复性中,要注意`引用数据类型和NaN
    // 两个对象都是不用的指针,所以没法去重
    const set1 = new Set([1, { name: '孙志豪' }, 2, { name: '孙志豪' }])
    console.log(set1);

    // 如果是两个对象是同一指针,则能去重
    const obj = { name: '我们一样' }
    const set23 = new Set([1, obj, 2, obj])
    console.log(set23);

    // NaN !== NaN,NaN是自身不等于自身的,但是在Set中他还是会被去重
    const set = new Set([1, NaN, 1, NaN])
    console.log(set);

 Map`对比`object`最大的好处就是,key不受`类型限制

 
定义map
const map1 = new Map()
 
新增键值对 使用 set(key, value)
map1.set(true, 1)
 
判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈')) 
 
获取map中某个key对应的value
console.log(map1.get(true)) 
 
删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
 
 
定义map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }

10、proxy 的理解

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 

11.  模块化开发简述

1. 概念是:将复杂代码按照功能的不同划分成不同模块单独维护,提高开发效率,降低维护成本。

2. JS 四大模块化规范简述

| 模块化规范 | 全局 | 模块化 | 导入函数 | 导出函数 | 加载页面机制 |---|---|---|---|---|

| ***monJS | 是 | 否 | require() | exports |  服务器同步加载模块方式,由于浏览器端使用网络请求的原因还是异步加载更合适

| AMD | 否 | 是 | define() | require() |  异步加载模块 在声明模块的时候加载 推崇依赖前置,在定义模块的时候就要声明其依赖的模块,预加载

| CMD | 否 | 是 | require() | exports |  同步加载模块方式,他也可以实现异步加载 CMD推崇就近依赖,只有在用到某个模块的时候再去require引入

| ES | 否 | 是 | import() | export |  异步 目前效率最好的规范 在script 标签上 type="module" 用于识别为Es6模块化

3. 基本规则

这是 Es6的规范

  • 一个文件就是一个模块 (且每一个模块只加载一次, 并只执行一次,重复加载同一文件,直接从内存中读取)
  • 每个模块都有单独的作用域,不会造成变量污染
  • 使用export导出成员
  • 通过import函数载入模块

 三 、Vue 2 主要部分

1. mvvm mvc 和mvp的区别?(Vue核心)

(1). MVVM —— Vue、React、AngularJS 等 的框架模型

MVVM 就是 Model-View-ViewModel 的缩写,MVVM 将视图和业务逻辑分开。

View:(视图层),Model (数据模型),而 ViewModel (视图模型视图模型是把两者(建立通信的桥梁)。

在 MVVM 框架下,(View 和 Model 之间没有直接的联系,而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的,因此 view 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的,互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来所以 View 和 Model 之间的同步工作完全是自动的,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理,

整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。

【优点】

数据源和视图实现了双向绑定,很好的做到了数据的一致性 相比于mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用。

【缺点】

因为使用了dataBinding(动态数据绑定),增加了大量的内存开销,增加了程序的编译时间,项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题

(2). MVC —— Python、PHP 包括 (AngularJS最初版本)等 的框架模型

全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
- Model(数据模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
- View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
- Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,也可以将Model的数据用View显示出来

【优点】

耦合性低,方便维护,可以利于分工协作 重用性高

【缺点】

使得项目架构变得复杂,对开发人员要求高

(3).  Mvp —— Java 等框架

MVP MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的

Controller 和 viewMoldel 的区别 控制器负责处理用户输入并引导应用程序的流程(具体流程:获取模型中的数据——>创建视图模型——>返回视图),而视图模型负责表示在视图中显示的数据 (输入什么显示什么)

2. Vue底层实现原理 (v-model 双向绑定 实现原理)

Vue.js 是采用数据劫持 结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调 Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则-视图(View)会自动更新。这种设计让状态管理变得非常简单而直观

(1). Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

(2). ***pile(指令解析器) : ***pile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新-视图

(3). Watcher(订阅者) : Watcher订阅者作为Observer和***pile之间通信的桥梁,主要做的事情是:

1. 在自身实例化时往属性订阅器(dep)里面添加自己

2. 自身必须有一个update()方法

3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发***pile中绑定的回调

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- <script src="https://cdn.jsdelivr.***/npm/vue@2.6.14/dist/vue.js"></script> -->
  </head>

  <body>
    <div id="app">
      <h3>姓名:{{name}}</h3>
      <h3>年龄:{{age}}</h3>
      <h3>info.a的值是:{{info.a}}</h3>
      <div>name的值:<input type="text" v-model="name" /></div>
      <div>info.a的值:<input type="text" v-model="info.a" /></div>
    </div>
    <script>
      class Vue {
        constructor(options) {
          this.$data = options.data;
          // 调用数据劫持的方法
          Observe(this.$data);
          // 属性代理
          Object.keys(this.$data).forEach(key => {
            Object.defineProperty(this, key, {
              enumerable: true,
              configurable: true,
              get() {
                return this.$data[key];
              },
              set(newValue) {
                this.$data[key] = newValue;
              }
            });
          });
          // 调用模板编译的函数
          ***pile(options.el, this);
        }
      }
      // 定义一个数据劫持的方法
      function Observe(obj) {
        // 递归的终止条件
        if (!obj || typeof obj !== "object") return;
        const dep = new Dep();
        // 通过Object.keys(obj) 获取到当前obj上的每个属性
        Object.keys(obj).forEach(key => {
          // 当前被循环的key所对应的属性值
          let value = obj[key];
          // 把value这个子节点,进行递归
          Observe(value);
          // 需要为当前的key所对应的属性,添加getter和setter
          Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
              Dep.target && dep.addSub(Dep.target);
              console.log(`有人读取了${key}的值`);
              return value;
            },
            set(newVal) {
              value = newVal;
              Observe(value);
              dep.notify();
            }
          });
        });
      }
      // 对HTML结构进行模板编译的方法
      function ***pile(el, vm) {
        // 获取el对应的DOM元素
        vm.$el = document.querySelector(el);
        const fragment = document.createDocumentFragment();
        while ((childNode = vm.$el.firstChild)) {
          fragment.appendChild(childNode);
        }
        // 进行模板编译
        replace(fragment);

        vm.$el.appendChild(fragment);

        function replace(node) {
          // 定义匹配插值表达式的正则
          const regMustache = /\{\{\s*(\S+)\s*\}\}/;
          // 证明当前的node节点是一个文本子节点,需要进行正则的替换
          if (node.nodeType === 3) {
            // 注意:文本子节点,也是一个DOM对象,如果要获取文本子节点的字符串内容,需要调用textContent属性获取
            const text = node.textContent;
            const execResult = regMustache.exec(text);
            if (execResult) {
              const value = execResult[1]
                .split(".")
                .reduce((newObj, k) => newObj[k], vm);
              node.textContent = text.replace(regMustache, value);

              new Watcher(vm, execResult[1], newValue => {
                node.textContent = text.replace(regMustache, newValue);
              });
            }
            // 终止递归的条件
            return;
          }

          // 判断当前的node节点是否为input输入框
          if (node.nodeType === 1 && node.tagName.toUpperCase() === "INPUT") {
            const attrs = Array.from(node.attributes);
            const findResult = attrs.find(x => x.name === "v-model");
            if (findResult) {
              const expStr = findResult.value;
              const value = expStr
                .split(".")
                .reduce((newObj, k) => newObj[k], vm);
              node.value = value;
              new Watcher(vm, expStr, newValue => {
                node.value = newValue;
              });
              // 监听文本框的input输入事件,拿到文本框最新的值,把最新的值,更新到vm上即可
              node.addEventListener("input", e => {
                const keyArr = expStr.split(".");
                const obj = keyArr
                  .slice(0, keyArr.length - 1)
                  .reduce((newObj, k) => newObj[k], vm);
                obj[keyArr[keyArr.length - 1]] = e.target.value;
              });
            }
          }
          // 证明不是文本节点,可能是一个DOM元素,需要进行递归处理
          node.childNodes.forEach(child => replace(child));
        }
      }
      // 收集依赖/收集订阅者
      class Dep {
        constructor() {
          // 今后,所有的watcher都要存到这个数组中
          this.subs = [];
        }
        // 向 subs 数组中,添加watcher的方法
        addSub(watcher) {
          this.subs.push(watcher);
        }
        // 负责通知每个watcher的方法
        notify() {
          this.subs.forEach(watcher => watcher.update());
        }
      }

      class Watcher {
        constructor(vm, key, cb) {
          this.vm = vm;
          this.key = key;
          this.cb = cb;
          Dep.target = this;
          key.split(".").reduce((newObj, k) => newObj[k], vm);
          Dep.target = null;
        }
        update() {
          const value = this.key
            .split(".")
            .reduce((newObj, k) => newObj[k], this.vm);
          this.cb(value);
        }
      }
      const vm = new Vue({
        el: "#app",
        data: {
          name: "zs",
          age: 20,
          info: {
            a: "a1",
            c: "c1"
          }
        }
      });
    </script>
  </body>
</html>

 3. Vue模版编译原理。

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,需要将template转化成一个JavaScript代码,这一个转化的过程,就成为模板编译。

转换过程 分为以下三步:

第一步是 将代码 (利用vue-template-***piler模块) 构建为AST(抽象语法树)

第二步是 对 AST 进行优化(例如,Vue 会将重复的元素合并成一个元素,并将条件判断的逻辑提前计算。 )

第三步是 将 AST 转换成 JavaScript 代码。(例如,Vue 会将 v-for 指令转换成 for 循环,并将 v-if 指令转换成 if 语句。)

4.  Vue 虚拟 dom 与 diff 算法

1. 虚拟 dom

        虚拟 DOM 是一种在内存中创建 DOM 树的技术。当我们的操作 使组件发生变化时 Vue 会计算出两个 DOM 树之间的差异,并只更新需要更新的部分。这可以大大提高 Vue 的性能。(当我们的操作重新生成的dom就是虚拟 DOM。)

2. diff 算法  (计算出两个 DOM 树之间的差异的过程

1. 将两个虚拟 DOM 树分解成单个节点并对每个节点进行比较,以确定它们是否相等。

2. 如果两个节点相等,则它们不需要更新,不相等,则需要更新它们。

3. 更新节点时,计算出需要更新的节点的子节点,进行递归比较,以确定哪些子节点需要更新。 

5. Vue3的 Proxy 相比于 Vue2的 defineProperty 的优势 

   在vue3 中
        Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法

Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。
    首先说一下 Object.defineProperty 的缺点:
        ① Object.defineProperty 无法监控到数组下标的变化导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
        ② Object.defineProperty 只能劫持对象的属性因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。


而要取代它的 Proxy 有以下两个优点
可以劫持整个对象,并返回一个新对象有多种劫持操作(13 种)
补充:
Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 mdn
Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。



问题: vue 中数组中的某个对象的属性发生变化,视图不更新如何解决?
 Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
 问题原因:因为 Vue2 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一:利用  this.set(this.obj,key,val)
例:this.set(this.obj,‘k1’,‘v1’)
方案二:就利用 Object.assign({},this.obj)创建新对象  
如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。 

Object.assign() 方法用于将一个或多个对象的属性复制到另一个对象中。它会将第一个参数的所有属性复制到第二个参数中,并覆盖第二个参数中已有的属性。如果第三个参数或更多参数存在,它们也会被复制到第二个参数中。

6. 渐进式框架的理解、Vue数据驱动的理解 

        渐进式框架:允许开发人员逐步采用和集成框架的不同功能和特性。这意味着你可以根据项目需求和团队的技术水平选择性地使用框架的不同部分,而不必一次性采用全部功能。(代码的迭代优化封装,效率高,复用性高

        Vue 的数据驱动:是指它采用了响应式的数据绑定机制结合使用虚拟 DOM 和 diff 算法来实现的。(将数据与 DOM 元素进行绑定,当数据发生变化时,Vue 会自动更新相关的 DOM 元素。)开发人员可以专注于数据的处理和逻辑,而无需手动操作 DOM。

7. Vue3.0 与 Vue2.0 的区别

1. 性能提升

        更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。

2. API 变动

        Vue2使用 选项类型API(Options API) 对比Vue3 组合式API(***position API)

        optionsApi 使用传统api中,新增一个需求,要在data,methods,***puted中修改

        ***positionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更灵活和可组合在一起

3. 声明周期变化

beforeCreate  -> setup()    开始创建组件之前,创建的是data和method
created    -> setup()       实例创建完成后
beforeMount  -> onBeforeMount   组件挂载到节点上之前 执行的函数。
mounted    -> onMounted 组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。
updated    -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount    实例销毁之前调用。
destroyed   -> onUnmounted  实例销毁后调用。
activated   -> onActivated  挂载后和更新前被调用的(也就是keep-alive组件激活时调用)。
deactivated  -> onDeactivated  keep-alive组件停用时调用、
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用

4.重写虚拟 DOM (Virtual DOM Rewrite、包括更快的渲染速度更小的包体积,通过使用虚拟 DOM 的优化算法和编译器的改进.

5. vue3 没有了过滤器

        双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了视图数据没发生变化 this.$set() vue3不需要

        双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:

       可直接监听数组类型的数据变

        监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升

        可直接实现对象属性的新增/删除

        响应更快

6. vue3新加入了TypeScript以及PWA支持

7. 更好的 Tree-shaking 支持:Vue 3.0 改进了模块的组织和打包方式,使得在打包应用时可以更好地进行 Tree-shaking,减少最终的包体积。

和 默认使用懒加载、可以不用加上key、vue3 的watch监听可以进行终止监听 等等

 8. vue中***puted、Watch区别

1. - ***puted 属性是基于依赖数据的自动计算属性,而 Watch 是通过监听数据变化来执行特定操作。

2. - ***puted 属性的值会被缓存,只有在依赖的数据变化时才会重新计算,而 Watch 每次数据变化都会执行回调函数。

3. - ***puted 适用于计算一个新的值,而 Watch 适用于执行异步或开销较大的操作

9. Vuex 3.0 —— Vue2状态管理工具(数据存储与共享)

        能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发效率

(1). 5 大核心方法(API)

        state:vuex的基本数据,数据源存放地,用于定义共享的数据。

        getter:从基本数据派生的数据,相当于state的计算属性

        mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是cmmi传过来的数据

        action:action是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用dispatch调用,然后在 action 中通过***mit 去调用 mutation 通过 mutation 去操作state。

        modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理 (一般用于较大项目)

(2). 4 大快捷语法糖 (mapState,mapActions,mapMutations,mapGetters)以下为部分示例

***puted: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])
***puted(){ ...mapState(['数据名字'])}


mounted(){
    ...mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.do***odosCount`
  doneCount: 'do***odosCount'
  })
    ...mapMutation(['方法名'])
}

...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),

10. Vue Router 路由(用于单页面的跳转)

Vue单页面:只有一个html页面,跳转方式是组件之间的切换,优点:跳转流畅、组件化开发、组件可复用、开发便捷缺点:首屏加载过慢

多页面:有多个页面,跳转方式是页面之间的跳转,优点:首屏加载块缺点:跳转速度慢

        原理:一般源码中,都会用到 window.history 和 location.hash 原理:通过改变浏览器地址URL,在不重新请求页面的情况下,更新页面视图,通过BOM中的location对象,其中对象中的location.hash储存的是路由的地址、可以赋值改变其URL的地址。而这会触发hashchange事件,而通过window.addEventListener监听hash值然后去匹配对应的路由、从而渲染页面的组件 1.一种是# hash,在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航 2.一种是h5的history,使用URL的Hash来模拟一个完整的URL

(1). 路由的 Hasn 模式和 History 模式  (默认是hash)

vue-router的实现原理(核心):更新视图但不重新请求页面。

1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。

2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题

3、history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。

(2). 路由的跳转方式 与 路由参数、params\name 传参区别

一. <router-link to="需要跳转到页面的路径"> 
(1). 不带参数
 
<router-link :to="{name:'home'}"> 
<router-link :to="{path:'/home'}"> //name,path都行, 建议用name  
// 注意:router-link中链接如果是'/'开始就是从根路由开始,如果开始不带'/',则从当前路由开始。
 
 (2).带参数
 
<router-link :to="{name:'home', params: {id:1}}">  
 
// params传参数 (类似post)
// 路由配置 path: "/home/:id" 或者 path: "/home:id" 
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留
 
// html 取参  $route.params.id
// script 取参  this.$route.params.id
 
 
<router-link :to="{name:'home', query: {id:1}}"> 
 
// query传参数 (类似get,url后面会显示参数)
// 路由可不配置
 
// html 取参  $route.query.id
// script 取参  this.$route.query.id

二. this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

1.  不带参数
 
this.$router.push('/home')
this.$router.push({name:'home'})
this.$router.push({path:'/home'})
 
 
 
2. query传参 
 
this.$router.push({name:'home',query: {id:'1'}})
this.$router.push({path:'/home',query: {id:'1'}})
 
// html 取参  $route.query.id
// script 取参  this.$route.query.id
 
 
 
3. params传参
 
this.$router.push({name:'home',params: {id:'1'}})  // 只能用 name
 
// 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留
 
// html 取参  $route.params.id
// script 取参  this.$route.params.id
 

3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数

query和params区别
        query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在
 
        params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失
 
        (1)
 query可以使用name和path而params只能使用name
        (2)  使用params传参刷新后不会保存,而query传参刷新后可以保存
        (3)  Params在地址栏中不会显示,query会显示
        (4)  Params可以和动态路由一起使用,query不可以 

(3). 路由守卫 

 参数

        to: Route: 即将要进入的目标 路由对象

        from: Route: 当前导航正要离开的路由对象

        next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

重定向 redirect:”/路径”

(1). 全局守卫(无论访问哪一个路径,都会触发全局的钩子函数)

1. router.beforeEach 全局前置守卫 进入路由之前

2. router.beforeResolve 全局解析守卫,在beforeRouteEnter调用之后调用

同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用

3. router.afterEach 全局后置钩子 进入路由之后

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

(2). 组件级守卫 

  • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到zhis

  • beforeRouteUpdate 路由复用同一个组件时

  • beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等

const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
     在渲染该组件的对应路由被 confirm 前调用
     不!能!获取组件实例 `this`
     因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    在当前路由改变,但是该组件被复用时调用
     举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
     由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
     可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
     导航离开该组件的对应路由时调用
     可以访问组件实例 `this`
  }
}

 (3).单个路由规则独享 的守卫 写在路由配置中,只有访问到这个路径,才能触发钩子函数

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      ***ponent: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

(4). 路由钩子函数执行顺序是什么 

一、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
二、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
​
​
完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

 (4). 路由配置 、 嵌套路由、路由懒加载

export default new Router({
    mode: 'history', //路由模式,取值为history与hash
    base: '/', //打包路径,默认为/,可以修改
    routes: [
    {
        path: string, //路径
        c***ponent: ***ponent; //页面组件
        name: string; // 命名路由-路由名称
        ***ponents: { [name: string]: ***ponent }; // 命名视图组件
        redirect: string | Location | Function; // 重定向
        props: boolean | string | Function; // 路由组件传递参数
        alias: string | Array<string>; // 路由别名

        children: Array<RouteConfig>; // 嵌套子路由

        // 路由单独钩子
        beforeEnter?: (to: Route, from: Route, next: Function) => void; 
        meta: any; // 自定义标签属性,比如:是否需要登录
        icon: any; // 图标
        // 2.6.0+
        caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false)
        pathToRegexpOptions: Object; // 编译正则的选项
    }
    ]
})
export default new Router({
    const routes = [
      {
        path: '/',
        name: 'Home',
        ***ponent: () => import('./***ponents/Home.vue');
      },
      {
        path: '/',
        name: 'Home',
        ***ponent: () => Promise.resolve({/* 组件定义对象 */})
        ***ponent: resolve => require(['@/***ponents/home'], resolve)
      },
      // 其他路由配置...
    ];
})
  

(5). 动态路由 示例

1. 在路由配置中定义动态路由:
const routes = [
  {
    path: '/user/:id',
    name: 'User',
    ***ponent: User***ponent,
  },
  // 其他路由配置...
];

2. 在组件中获取和处理路由参数:
export default {
  name: 'User***ponent',
  created() {
    const userId = this.$route.params.id;
    // 根据路由参数执行特定的操作,例如获取用户信息
    this.getUser(userId);
  },
  methods: {
    getUser(userId) {
      // 根据用户 ID 获取用户信息的逻辑
    },
  },
};

3. 项目中设定

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)​​

const routes = [
  {
    path: '/login',
    name: 'login',
    ***ponent:  ***ponent: () => import('../view/Login/Login.vue'),
  },
  // 其他路由配置...
];


const router = new VueRouter({
	mode: 'history',
	routes,
})​
//前置守卫拦菜单数据
router.beforeEach((to, from, next) => {
    const token = Cookies.get("token");
    if (token && store.state.menus.length === 0) {
        store.dispatch("loginInfoHandler").then(() => {
            getRouter();
            // console.log(activeRouter);
            next(to.path); //由于先执行同步的问题,所以同步next执行下一步的时候没有处理路由,所以要在继续执
            //行下一步的时候重新指向进去页面的那个路由
        }); //异步操作,先执行同步再执行异步
    } else if (
        token &&
        store.state.menus.length !== 0 &&
        from.path == "/login" &&
        to.path == "/homePage"
    ) {
        getRouter();
        next("/index");
    } else if (!token && to.path !== "/login") {
        next("/login");
    } else if (token && to.path == "/login") {
        next(from);
    } else if (to.path == null) {
        next('/404'); //反之继续执行下一步
    } else {
        next(); //反之继续执行下一步
    }
});


const setRouter = () => {
    return newRouter.forEach(v => {
        v.children = routerChildren(v.children);
        v.***ponent = router***(v.***ponent);
        router.addRoute(v); //添加
    })

}
function router***(path) { //对路由的***ponent解析
    return (resolve) => require([`@/views/${path}`], resolve);
}
function routerChildren(children) { //对子路由的***ponent解析
    children.forEach(v => {
        v.***ponent = router***(v.***ponent);
        if (v.children != undefined) {
            v.children = routerChildren(v.children)
        }
    })
    return children
}
export default router​

11. Vue2 指令方面

1. Vue 的修饰符 和 一些常见的指令

1. 事件修饰符
    
    .stop  阻止事件冒泡
    .cpture 设置事件捕获
    .self  只有当事件作用在元素本身才会触发
    .prevent 阻止默认事件,比如超链接跳转
    .once 事件只能触发一次
    .native 触发js原生的事件
    .passive 移动端解决 滚动问题 相当于给scoll事件上添加了一个 .lazy 修饰符,这样不会导致滚动事件一直触发

  <button @click.enter="handleEnterKey" />
  <input @click.esc="handleEscKey" />

2. 按键修饰符

   -  .enter :监听回车键事件。 
   -  .tab :监听 Tab 键事件。 
   -  .delete :监听删除键事件。 
   -  .esc :监听 Esc 键事件。 
   -  .space :监听空格键事件。 
   -  .up 、 .down 、 .left 、middle、 .right :监听方向键事件。 

  <input @keyup.enter="handleEnterKey" />
  <input @keyup.esc="handleEscKey" />

3. 系统修饰符

    可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

    .ctrl (实测结果:由于mac系统 ctrl+鼠标左键 = 右键 这个功能,所以mac上无法实现)

    .alt

    .shift

    .meta (meta在win上代表⊞键,在mac上代表⌘键)


    <!-- Alt + C -->
    <input v-on:keyup.alt.67="doSomething">
 
    <!-- Ctrl + Click -->
    <div v-on:click.ctrl="doSomething">Do something</div>

4. 表单修饰符

   -  .lazy :将输入事件改为使用  change  事件而不是  input  事件。 
   -  .number :将输入值转换为数字类型。 
   -  .trim :自动去除输入值两端的空白字符。 
   -  .number 把文本框的内容转换为数字
   -  .trim  去除文本框左右空格

    <input v-modle.number="doSomething">
    <input v-modle.trim="doSomething">

5. 事件语法糖 .sync 修饰符

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.jsdelivr.***/npm/vue@2.6.14/dist/vue.js"></script>
  </head>

  <body>
    <div id="app">
      {{ info }}
      <son*** v-bind:actinfo.sync="info"></son***>
    </div>
    <script>
      Vue.***ponent("son***", {
        template: `<div v-on:click="printInfo()">61616161</div>`,
        props: {
          getsonactive: { type: Function, default: () => "" }
        },
        methods: {
          printInfo() {
            this.$emit("update:actinfo", "修改了");
          }
        }
      });
      new Vue({
        el: "#app",
        data: { info: "666" }
      });
    </script>
  </body>
</html>

 指令

⑴v-bind:给元素绑定属性
⑵v-on:给元素绑定事件
⑶v-html:给元素绑定数据,且该指令可以解析 html 标签
⑷v-text:给元素绑定数据,不解析标签
⑸v-model:数据双向绑定
⑹v-for:遍历数组
⑺v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素
⑻v-else:条件渲染指令,必须跟 v-if 成对使用
⑼v-else-if:判断多层条件,必须跟 v-if 成对使用
⑽v-cloak:解决插值闪烁问题
⑾v-once:只渲染元素或组件一次
⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度
⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)

 2. v-for 与 v-if 的优先级 、 (v-if和v-show的区别及使用场景)

v-for 比 v-if 优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。如果需要的话在外层标签加一个template标签包一下就行了

v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着

v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none

v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if 

3. vue中key 的作用 

        “key 值:用于管理可复用的元素。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求。 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 是必须的。”

        key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,更高效的对比虚拟DOM中每个节点是否是相同节点,相同就复用,不相同就删除旧的创建新的

   例子:     假如做了一个表格,里面有3条数据,并且用索引作为key,这时候我们添加了一条,那么这条数据的索引就会变为 0 ,导致索引和旧时dom的第一条索引一致,所以diff在进行计算的时候,会将真实dom的值直接进行覆盖,导致 新加的一条的值显示的是 旧时dom的第一条的值,旧时dom的最后一条的值空了。(数据不会错,页面会显示异常),然而id都是唯一的,在比对的时候 一定不会认为重复 所以会重新渲染dom 不会出错

4. v-clock(Vue内置指令) 指令防止插值闪烁

(1)使用:使用时搭配Css样式 [v-clock]{ display:none; }

(2)出现场景示例当页面第一次加载或模板发生更改时,Vue需要重新编译模板。可能会看到只有模板的HTML结构,而样式和JavaScript尚未加载,导致差值闪烁。

(3)工作原理:它只是通过CSS将元素隐藏起来,然后通过Vue实例的挂载完成事件将其显示出来。

5. 自定义指令(directive\directives),自定义过滤器(filter、filters)、自定义属性 (dataset

     (1).   自定义指令:分为 全局自定义指令 Vue.directive('name',{ 操作 }) 与 局部自定义指令directives: {name: {操作}}

 钩子函数参数
   1. el:指令所绑定的元素,可以用来直接操作 DOM。
   2. binding:一个对象,包含以下 property:
        name:指令名,不包括 v- 前缀。
        value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
        oldValue:指令绑定的前一个值,仅在 update 和 ***ponentUpdated 钩子中可用。无论值是否改变都可用。
        expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
        arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
        modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo:     true, bar: true }。
   3. vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
   4. oldVnode:上一个虚拟节点,仅在 update 和 ***ponentUpdated 钩子中可用。


    除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素    的 ( dataset )来进行。

 dataset(自定义属性)

<div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth></div>

<script>
    const el = document.getElementById('user')
    console.log(el.dataset)
//
    dateOfBirth: ""
    id: "1234567890
    "user: "johndoe"
</script>
全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})


局部自定义指令

<template>
  <div>
    <p>Hello World</p>
    <p v-my-directive="message">This is a custom directive</p>
动态参数
    <div id="user" v-demos:[message]="message" >1111</div>
    <div id="user" v-demos:foo.a.b="message" >1111</div>
对象字面量
    <div v-demo="{ color: 'white', text: 'hello!' }"></div>
  </div>
</template>

<script>
export default {
  directives: {
    myDirective: {
      bind (el, binding, vnode) { // 绑定时钩子,当指令被绑定到元素时会调用。
        console.log('绑定', '只调用一次,指令第一次绑定到元素时调用')
      },
      inserted (el, binding, vnode) {
        console.log('插入', '被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中')
      },
      update (el, binding, vnode, oldVnode) {
        console.log('更新', '所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。')
      },
      ***ponentUpdated (el, binding, vnode, oldVnode) {
        console.log('更新', '指令所在组件的 VNode 及其子 VNode 全部更新后调用。')
      },
      unbind (el, binding, vnode) { // 解绑时钩子,当指令从元素上解绑时会调用。
        console.log('解绑', '只调用一次,指令与元素解绑时调用。')
      }
    },
函数简写:bind 和 update 时触发相同行为,而不关心其它的钩子
    Vue.directive('color-swatch', function (el, binding) {
      el.style.backgroundColor = binding.value
    })
  },
};
</script>
补充 : vue3的自定义指令钩子函数?
​
created - 自定义指令所在组件, 创建后
beforeMount - 就是Vue2.x中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
mounted - 就是Vue2.x中的 inserted, 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
beforeUpdate - 自定义指令所在 DOM, 更新之前调用
updated - 就是Vue2.x中的 ***ponentUpdated
beforeUnmount - 销毁前
unmounted - 销毁后

 为什么Vue3取消了过滤器? (Vue 3 中,我们可以使用组件来实现过滤器的功能)

* 过滤器是 Vue 1 时代的产物,在 Vue 2 中已经被组件替代。

* 过滤器的使用不够直观,而且在复杂场景下很容易出错。

* 过滤器的性能不如组件。

(2). 自定义过滤器 filter分为全局Vue.filter('name',function(){})和局filters:{name:{function(){}}}  可以用来统一单位,比如:时间、货币、数据类型等
1. 使用
管道符 | 前面是变量 后面是过滤器

在双花括号中 -->
{{ message | capitalize }}

接参
{{ message | filterA('arg1', arg2) }}

在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

过滤器串联  (filterA过滤完变量之后在传到过滤器filterB进行过滤)-->
{{ message | filterA | filterB }}


2. 定义

局部、
filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}
全局、
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

12 . Vue2 组件方面

自定义组件
        在vue 中的 ***ponent 中新建组件,定义好<template>视图层,<script>逻辑层,<style>css样式层。,然后在页面引入,在***ponents 中注册组件 ,在页面中作为标签来使用。

        在vue中开发,都是用的组件化的思想开发的,组件封装的方式可以使我们的开发效率提高,把单页面的每个模块拆分为一个组件件,

        组件封装的方式解决了我们传统项目,开发效率低,难以维护,复用性低等问题。

        使用:比如说封装一个 swiper 首先我们要定义一个props要接受传递的数据,写入响应的逻辑,在通过import引入到页面注册作为标签使用即可。 

1. 组件之间的通信(子传父父传子同级通信(兄弟通信跨级通信全局通信

(1).父传子

        1. 在父组件的 子组件标签上 用自定义属性来传递变量,2. 在子组件中用 props 接受当作普通变量使用即可

1. 父组件
<template>
    <div class="home"><Son :transferValue="transferValue"></Son></div>
</template>

<script>
import Son from '@/***ponents/Son'
export default {
  name: 'home',
  ***ponents: {Son},
  data () { return { transferValue: 'Hello Son' } }
}
</script>

2. 子组件
<template>
  <div class="Son">  <h1>{{transferValue}}</h1> </div>
</template>

<script>
export default {
  props: { transferValue: { type: String, default: 'Null' } }
}
</script>

(2).子传父 emit

        1. 在子组件定义 触发传递事件、父组件的接受事件名。2. 在父组件中的子组件标签上 自定义一个事件 名称为组件 emit 中传递的事件。3. 通过函数形参得到值

1. 父组件
<template>
    <div class="home"><Son @fatherMthod="fatherMthod"></Son><h1 v-html="showText"></h1></div>
</template>

<script>
import Son from '@/***ponents/Son'
export default {
  name: 'home',
  ***ponents: {Son},
  data () { return { showText: '' } },
  methods: { fatherMthod (sonTransferValue) { this.showText = sonTransferValue } }
}
</script>

2. 子组件
<template>
  <div class="Son"> <button @click="handleClick">子组件按钮</button> </div>
</template>

<script>
export default {
  props: { transferValue: { type: String, default: 'Null' } },
  methods: {
    handleClick () { this.$emit('fatherMthod', `传递成功`) }
  }
}
</script>

(3).兄弟通信 bus.emit / bus.on

        1.  到 Vue的原型上配置一个全局事件 bus. 2. 兄弟A用 this.$bus.$emit('methodsName','数据') 抛出一个事件 。3. 兄弟B用 this.$bus.$on('methodsName',回调函数) 接收数据

Main.js 配置 Vue.prototype.$bus = new Vue()

1. 父组件
<template>
  <div class="center"><A></A><B></B></div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
import B from '@/views/Home/***ponents/B'
export default { name: 'center', ***ponents: { A, B } }
</script>


2. 父组件下的   兄弟A
<template>
    <div class="A" @click="getLink">A</div>
</template>

<script>
export default {
  name: 'A',
  methods: { getLink () { this.$bus.$emit('ATransfer', 'A传递') } }
}
</script>

3. 父组件下的   兄弟B

<template>
    <div class="about">B收到{{showText}}</div>
</template>

<script>
export default {
  name: 'about',
  data () { return { showText: '' } },
  mounted () { this.$bus.$on('ATransfer', (AValue) => { this.showText = AValue }) }
}
</script>

(4) . 跨级通信:provide / inject    (允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效)

1. 祖父
<template>
  <div class="center">1<A></A></div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
export default { name: 'center',
  ***ponents: { A },
  provide: {
    centerValue: { type: String, default: 'center传递' }
  }
}
</script>

2. 父
<template>
    <div class="A">A  <GrandSon></GrandSon> </div>
</template>

<script>
import GrandSon from '@/views/Home/***ponents/GrandSon'
export default {
  name: 'A',
  ***ponents: {GrandSon}
}
</script>

3. 子
<template>
    <div class="GrandSon">A的儿子,center的孙子</div>
</template>

<script>
export default {
  name: 'GrandSon',
  inject: ['centerValue'],
  mounted () { console.log(this.centerValue) }
}
</script>

(5). Vuex 全局数据共享   、或者全局变量数据共享

store 文件

const state = {
    ***monData:true
}
 

vue 文件


mounted(){
    ...mapState['***monData']
}



main.js 定义全局变量

import Vue form 'vue

import axios from 'axios'

Vue.prototype.$axios = axios

new Vue({
  el: '#app', 
  template: '<App/>'
})

2. keep-alive 缓存组件 (保留dom,防止重新渲染)

组件使用keep-alive以后会新增两个生命周期 actived() deactived()

activated(组件激活时使用) 与 deactivated(组价离开时调用)

  • Props

    • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
    • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
    • max - 数字。最多可以缓存多少组件实例。
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <***ponent :is="view"></***ponent>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <***ponent :is="view"></***ponent>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']"  :max="10">
  <***ponent :is="view"></***ponent>
</keep-alive>

当离开当前路由时,会直接调用beforeDestroy和destroyed来销毁。 当组件设置keep-alive后,不会直接调用这个销毁周期函数,而是会在生命周期函数新增两个,activated和deactivated; 当退出的时候会执行deactivated 函数 

3. Vue Mixin混入

        在我们需要在同一个生命周期中做同一件事的时候,可以用 mixin 来实现一个公共的方法引入使用。minxin 函数优先执行,如有冲突则以组件中的方法为准

 定义一个混入对象 也可以是一个函数
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

var mixinHandle = (params) => { 
    return {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('params') // 1
        }
      }
    }
}

在实例中使用
new Vue({
  mixins: [myMixin,mixinHandle(this.params)],
  data(){return{params:{a:1}}}
  created: function () {
    console.log('组件钩子被调用')
  }
})

4. Vue组件中的data为什么是一个函数

Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了,这就造成了数据污染。如果data是一个函数,每个实例的data都在闭包中,就不会各自影响了

5. Vue 插槽 Slot 

        使用方法是 在子组件中放入一个 放入slot标签(等于开辟了一道空间 , 供父组件传入内容),然后 在父组件中的 子组件标签下级 用slot 标签 接一下子组件开辟的空间 ,在这一片空间可以写相应的内容, 这样就(可以利这片空间 在相同的子组件实现不同的效果)。

        案例: 假如 我们的页面有多个背景墙,并要求背景墙的背景一样,但内容各式各样,这时我们就可以用子组件封装了一个背景墙 用插槽来装填这些内容。(减少了判断的需要,提高了运算效率,代码方面也很整洁。跟回调函数相似。)

·       作用域:和其它标签作用域相同,不设置作用域插槽 访问不到子组件的作用域

(1). 默认插槽 、 具名插槽 、作用域插槽 (包含 独占插槽 和 解构插槽)
1. 默认插槽
    父组件 .
<template>
  <div class="center">
    <A> <slot>定义内容</slot> </A>
  </div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
export default { name: 'center', ***ponents: { A } }
</script>

子组件 .
<template>
  <div class="A" @click="getLink">
    <slot></slot>
  </div>
</template>

<script>
export default { name: 'A'}
</script>

2. 具名插槽
    父组件 .
<template>
  <div class="center">
    <A>
       (1)  这个是 verson 2.6.0 之前的版本
      <template slot="left">
        <div> 左侧内容{{params}}</div>
      </template>
       <template slot="right">
        <div> 右侧内容是{{params}}</div>
      </template>
        现在可以这么写
      <template v-slot:left>
        <div> 左侧内容{{params}}</div>
      </template>
       <template v-slot:right>
        <div> 右侧内容是{{params}}</div>
      </template>
       (2)  简写形式
       <template #right>
        <div> 右侧内容是{{params}}</div>
      </template>
    </A>
  </div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
export default { name: 'center', ***ponents: { A } }
</script>

子组件 .
<template>
  <div class="A" @click="getLink">
    <slot name="left"></slot>
    <slot name="right"></slot>
  </div>
</template>

<script>
export default { name: 'A'}
</script>

3. 作用域插槽
    父组件 .
<template>
  <div class="center">
    <A>
     (1)    这个是 verson 2.6.0 之前的版本
      <template slot="left" slot-scope="params">
        <div> 左侧内容{{params.value}}</div>
      </template>
       <template slot="right"  slot-scope="params">
        <div> 右侧内容是{{params.value}}</div>
      </template>
        现在可以这么写
      <template #left="params">
        <div> 左侧内容{{params.value}}</div>
      </template>
       <template #right="params">
        <div> 右侧内容是{{params.value}}</div>
      </template>
      (2)   (独占插槽)  若是子组件只提供一个默认插槽 以这么写
      <template v-slot="params">
        <div> 左侧内容{{params}}</div>
      </template>
      (3)   (解构插槽)  若是子组件上有多个属性 这么写
      <template #left="{ valueTop , valueBottom }">
        <div> 上侧内容是{{valueTop}}下侧内容是{{valueBottom}}</div>
      </template>
    </A>
  </div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
export default { name: 'center', ***ponents: { A } }
</script>

子组件 .
<template>
  <div class="A" @click="getLink">
    <slot name="left" :value="'left'" :valueTop="'上'" :valueBottom="'下'"></slot>
    <slot name="right" :value="'right'"></slot>
  </div>
</template>

<script>
export default { name: 'A'}
</script>
 2.  动态插槽 (可以控制插槽名称的变化来动态渲染插槽)
1. 父组件
<template>
  <div class="center">
    <button @click="key == 'right' ? key = 'left' : key = 'right'">切换显示内容</button>
    <A>
      <template  v-slot:[key]="row" >
        现在切换到了{{key}}的内容是{{row.value}}
      </template>
    </A>
  </div>
</template>

<script>
import A from '@/views/Home/***ponents/A'
export default { name: 'center', ***ponents: { A }, data () { return { key: 'right' } } }
</script>


2. 子组件
<template>
  <div class="A">
    <slot name="left" :value="'left'" :valueTop="'上'" :valueBottom="'下'"></slot>
    <slot name="right" :value="'right'"></slot>
  </div>
</template>
3.  scoped 原理及 穿透方法

        vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记:data-v-hash 的方式,以保证唯一(通过 PostCSS 转译),达到样式私有模块化的目的。

        scoped 的 3 条渲染规则: ① 给 HTML 的 DOM 节点加一个不重复的 data 属性,来表示它的唯一性; ② 在每句 css 选择器末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器来私有化样式; ③ 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上 ddan 当前组件的 data 属性。

如要修改组件或样式文件样式,可以使用样式穿透方法

在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢?
①不使用scopeds省略(不推荐);
② 在模板中使用两次style标签。
③scoped穿透: css 穿透 >>> 、 scss 穿透  /deep/ 、::v-deep

13. -----------------------------------------------------Vue环境方面 -------------------------------------------------

 1. Vue 封装axios请求、配置 跨域 与 多环境变量

(1).axios封装
import axios from 'axios'
// 请求头配置基础、 公共的配置( baseUrl, timeOut, withcookies)
const service = axios.create({
  baseUrl: 'process.env.VUE_APP_BASE_API',
  // withCredentials: true, // 跨域的状态下是否携带参数
  timeout: 6000 // 超时时间
})
// 也可以这样写axios.defaults.baseURL = processs.evn.BUE_APP_BASE_URL,

// 请求拦截
service.interceptors.request.use(config => {
  // 每次发送请求要进行的公共的操作
1. loading 加载 等等
  // token
2.Token  // let token = Window.localStorage.getItem('token')
  // config.header.token = token || ''
  // 标准写法
  // config.headers['Authorization'] = 'Bearer '+ token
  return config
}, err => {
  throw Error(err)
  // 或者
  // Promise.reject(err)
})

// 响应拦截
service.interceptors.response.use(res => {
  return Promise.resolve(res)
}, err => {
  return Promise.reject(err)
})

export default service
import service from '@/utils/axios';


    service.get('/a/b').then(({data}) => {
      console.log(data)
    }).catch((err) => { console.log(err) })

(2).配置环境变量

在Vue项目的根目录下创建环境变量配置文件,命名为.env.xxx,其中xxx表示环境名称,例如.env.development和.env.production。内容如下

VUE_APP_API_URL=http://api.example.***
(3).配置跨域 (在打包上线之前,需要根据不同的环境进行配置。)
 在vue.config.js文件中,使用proxy选项来配置跨域代理。例如,将所有以/api开头的请求代理到http://localhost:3000:
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
}
(4).配置命令
开发环境: 在package.json文件中的scripts部分,添加一个dev命令,用于启动开发环境的服务器。例如:
"scripts": {
  "dev": "vue-cli-service serve --mode development"
}
然后,在开发环境中使用npm run dev命令启动服务器。
生产环境: 在package.json文件中的scripts部分,添加一个build命令,用于打包生产环境的代码。例如:
"scripts": {
  "build": "vue-cli-service build --mode production"
}
然后,在生产环境中使用npm run build命令进行打包。

2. Vue目录 assets和static和public三个静态资源文件 区别

(1).assets 目录:

- 用途assets 目录用于存放项目中的静态资源,例如 样式文件(CSS)图片字体文件等。

- 特点:这些资源会经过 webpack 构建过程,并且会被模块化处理。在组件中引用这些资源时,可以使用 相对路径 来引用,Vue CLI 会自动处理路径。

- 注意: assets 目录中的资源会被 webpack 打包,因此会受到构建过程的影响。

(2).static 目录:

- 用途: static 目录用于存放静态资源,例如图片字体文件第三方库等。

- 特点:这些资源会被复制到构建输出的根目录下,但不会经过 webpack 构建过程。

- 注意: static 目录中的资源不会被 webpack 处理,因此不会受到构建过程的影响。

(3).public 目录:

- 用途public 目录用于存放静态资源,例如 HTML 文件图标文件等。

- 特点:这些资源会被复制到构建输出的根目录下,但不会经过 webpack 构建过程。

- 注意:在 HTML 文件中引用这些资源时,需要使用 绝对路径 来引用

如果资源支持线上访问 可以使用.static和public

3. Vue 框架特点 

        1. 响应式数据绑定:Vue 使用了双向绑定的数据模型,可以轻松地将数据与视图进行同步。当数据发生变化时,视图会自动更新,而用户的交互操作也可以直接影响数据的变化。

        2. 组件化开发:Vue 提供了组件化的开发方式,将页面拆分为独立的组件,每个组件都有自己的模板、逻辑和样式。这样可以提高代码的可维护性和复用性,并且可以更好地组织和管理复杂的应用程序。

        3. 虚拟 DOM:Vue 使用虚拟 DOM 技术,通过在内存中构建一个轻量级的虚拟 DOM 树来代表真实的 DOM 结构。这样可以减少直接操作真实 DOM 的开销,提高性能,并且可以进行高效的 DOM 更新和渲染。

        4. 生态系统丰富:Vue 拥有一个活跃的社区和丰富的生态系统,提供了许多插件、工具和第三方库,帮助开发者更快地构建应用程序。Vue 也有大量的文档和教程资源,使学习和使用 Vue 变得更加容易。

        5. 渐进式框架:Vue 是一个渐进式框架,可以根据项目的需要逐步引入。你可以选择只使用 Vue 的核心库,或者在需要时逐步引入更多的功能和插件。这使得 Vue 可以灵活地适应各种项目的需求。

4. 首屏、项目优化 

1. 使用异步组件:异步组件可以延迟加载组件,避免首屏加载过多组件,造成卡顿。

<script>

import Loading***ponent  from './Loading.vue';
import Error***ponent  from './Error.vue';

export default {

    name: 'Demo',
    
    data() {
        return {
        
            async4Show: false,
            
        }
    },
    
    ***ponents: {
       
        my-ele: ()=>{
            return {
                // 需要加载的组件 (应该是一个 `Promise` 对象)
                ***ponent: import('@/test/Async4'),
                // 异步组件加载时使用的组件
                loading: Loading***ponent,
                // 加载失败时使用的组件
                error: Error***ponent,
                // 展示加载时组件的延时时间。默认值是 200 (毫秒)
                delay: 200,
                // 如果提供了超时时间且组件加载也超时了,
                // 则使用加载失败时使用的组件。默认值是:`Infinity`
                timeout: 500
            }
        },
    }
}
</script>

2. 使用按需加载:按需加载可以只加载当前页面需要的组件,避免加载不需要的组件,造成首屏卡顿。

3. 使用懒加载:懒加载可以延迟加载组件,避免首屏加载过多组件,造成卡顿。

4. 使用预加载:预加载可以提前加载组件,避免用户点击时需要等待组件加载,造成卡顿。 5. 使用缓存:缓存可以避免重复加载组件,减少首屏加载时间。

6. 使用性能分析工具:性能分析工具可以帮助我们分析页面性能,找到性能瓶颈,并针对性地进行优化。

7.尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher 

8.合理运用虚拟dom机制,场景 v-for 遍历必须加 key,key 最好是 id 值,

9.注意冗余变量或者事件未被使用

Vue 项目优化可以从以下几个方面入手:

1. 使用 Vue CLI 构建项目:Vue CLI 可以帮助我们快速构建 Vue 项目,并提供了一系列优化建议。

2. 使用 Vuex 管理状态:Vuex 可以帮助我们集中管理应用程序的状态,避免状态污染,提高应用程序的可维护性。

3. 使用 Vue Router 管理路由:Vue Router 可以帮助我们灵活地管理路由,避免页面跳转时重新加载页面,提高页面性能。

4. 使用 Vuex-PersistedState 持久化状态:Vuex-PersistedState 可以帮助我们持久化 Vuex 中的状态,避免用户刷新页面时状态丢失。

5. 使用 Vue SSR 服务端渲染:Vue SSR 可以将 Vue 页面在服务端渲染,提高页面性能。

6. 使用 Vue CDN 加速:Vue CDN 可以帮助我们加速 Vue 组件的加载,提高页面性能。

7.防抖、节流 第三方模块按需导入

8.图片懒加载

当打开一个有很多图片的页面时,先只加载页面上可视区域的图片,等滚动到页面下面时,再加载所需的图片。这就是图片懒加载。

1.document.documentElement.clientHeight获取屏幕可视窗口大小;
2.document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离
3.判断当滚动条滚动到一定高度的时候就进行图片懒加载;


    let lazyImages = [...document.querySelectorAll('.lazy-image')]
    let inAdvance = 300 // 自定义一个高度,当距离300px到达图片时加载
    function lazyLoad() {
        lazyImages.forEach(image => {
            if (image.offsetTop < window.innerHeight + window.pageYOffset + inAdvance) { // 距离xxpx时加载图片
                image.src = image.dataset.src
                image.onload = () => image.classList.add('loaded')
            }
        })
    }
    lazyLoad()
    window.addEventListener('scroll', _.throttle(lazyLoad, 16)) // 用到了lodash的节流函数
    window.addEventListener('resize', _.throttle(lazyLoad, 16))

打包.压缩代码 vue-cli-plugin-***pression

const ***pressionPlugin = require("***pression-webpack-plugin");
module.exports = {
//设置为false以加速生产环境构建
  productionSourceMap: false,
 configureWebpack: {
    plugins: [
    new ***pressionPlugin({
        /* [file]被替换为原始资产文件名。
           [path]替换为原始资产的路径。
           [dir]替换为原始资产的目录。
           [name]被替换为原始资产的文件名。
           [ext]替换为原始资产的扩展名。
           [query]被查询替换。*/
        filename: '[path].gz[query]',
        //压缩算法
        algorithm: 'gzip',
        //匹配文件
        test: /\.js$|\.css$|\.html$/,
        //压缩超过此大小的文件,以字节为单位
        threshold: 10240,
        minRatio: 0.8,
        //删除原始文件只保留压缩后的文件
        deleteOriginalAssets: true
      })
    ]
  }
}

 5. CDN配置方法 

(1).自己配置

在Vue项目中,可以通过配置Webpack来引入CDN资源。具体步骤如下:

  1. 打开项目的webpack配置文件,一般是webpack.config.js或者vue.config.js
  2. 在配置文件中找到externals字段,如果没有则需要手动添加。
  3. externals字段中添加需要引入的CDN库,以键值对的形式表示。键是库的名称,值是在全局作用域中暴露的变量名。
  4. 在HTML模板文件中,手动引入CDN链接,可以通过<script>标签的src属性来引入。

下面是一个示例的Webpack配置文件:

module.exports = { 
// ... externals: { 
vue: 'Vue', 
'vue-router': 'VueRouter', 
jquery: '$'
 },
 // ... 
}

在HTML模板文件中,可以通过<script>标签来引入CDN链接:

<!DOCTYPE html>
 <html>
 <head>
 <!-- ... --> </head>
 <body> 
<!-- ... -->
 <script src="https://cdn.jsdelivr.***/npm/vue"></script> 
<script src="https://cdn.jsdelivr.***/npm/vue-router"></script> 
<script src="https://cdn.jsdelivr.***/npm/jquery">
</script>
 </body> 
</html>

这样配置后,Webpack在开发运行或打包构建时,就不会将这些CDN库打包到最终的app.js文件中,而是直接从CDN链接中加载。

(2).html插件配置(npm install --save-dev html-webpack-plugin)
const HtmlWebpackPlugin = require('html-webpack-plugin');

// ...

plugins: [
  new HtmlWebpackPlugin({
    template: 'index.html',
    filename: 'index.html', // 输出html文件的路径
    inject: 'body', // 注入脚本的位置,默认为body
    favicon: 'favicon.ico', // favicon路径
    chunks: ['main'], // 需要注入的chunk名称
    minify: {
      remove***ments: true,
      collapseWhitespace: true,
      removeAttributeQuotes: true
      // ...其他minify选项
    },
    // 自定义添加script标签
    scripts: [
      {
        src: 'https://cdn.jsdelivr.***/npm/jquery@3.6.0/dist/jquery.min.js',
        async: false
      },
      {
        src: 'https://cdn.jsdelivr.***/npm/vue@2.6.14/dist/vue.min.js',
        async: false
      },
      // 添加其他script标签...
    ]
  })
]

在上面的例子中,我们通过scripts属性自定义添加了两个script标签,分别是jQuery和Vue.js的cdn链接。这些script标签会在生成的html文件中自动添加。

 6. 数据加密、地址栏URL参数加密

1. CryptoJS:一个流行的JavaScript加密库,支持多种加密算法,包括AES、DES、RSA等。它提供了简单的API和示例代码,可以轻松地在前端应用中实现加密和解密功能。CryptoJS的文档和社区支持也非常好,适合有一定加密知识的开发者使用。

2. SJCL:一个轻量级的JavaScript加密库,支持对称加密、非对称加密、哈希函数等多种加密算法。SJCL的设计目标是简单、快速和安全,适合用于对性能要求较高的前端应用。

3. Forge:一个全面的JavaScript密码学库,支持对称加密、非对称加密、数字签名等多种加密算法。Forge的代码库非常庞大,但也提供了非常详细的文档和示例代码,适合有一定加密经验的开发者使用。

4. AES加密.js:一个用于在浏览器中实现AES加密的JavaScript库,支持多种模式和填充方式。AES加密.js的API设计简单,适合用于实现基本的AES加密功能。

5. encrypt.js:一个用于在浏览器中实现多种加密算法的JavaScript库,包括对称加密和非对称加密。encrypt.js的代码库比CryptoJS小,适合用于简单的加密需求。

6. Base64.js:一个用于在浏览器中实现Base64编码和解码的JavaScript库。Base64.js的API设计简单,适合用于实现基本的Base64编码和解码功能。

import CryptoJS from 'crypto-js';

export function decrypt(word) {
    if (!word) {
        return '';
    }
    var keyHex = CryptoJS.enc.Utf8.parse('***DJ!@#$%');
    var ivHex = CryptoJS.enc.Utf8.parse('20231106');
    var decrypted = CryptoJS.DES.decrypt(
        {
            ciphertext: CryptoJS.enc.Base64.parse(word)
        },
        keyHex,
        {
            iv: ivHex,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }
    );
    return decrypted.toString(CryptoJS.enc.Utf8);
}

一般使用encrypt,大型项目使用CryptoJS,encrypt体积小且安全,CryptoJS更加全面与方便,体积大

地址栏URL参数加密
加密
window.btoa('username=admin&password=123456'); 

解密
window.encodeURI***ponent('username=admin&password=123456');


地址栏参数原生获取
export function UrlSearch() {
    let name, value, str = location.href, num = str.indexOf("?");
    console.log(location.hash);表示URL中#后的部分,用于指定页面中的一个特定位置
    console.log(location.hostname);表示URL中主机名的部分。
    console.log(location.host);表示URL中主机名和端口号的部分。
    console.log(location.pathname);表示URL中指向页面资源的路径部分。
    console.log(location.port);表示URL中指定的端口号。
    console.log(location.protocol);表示URL中协议(http,https等)的部分。
    console.log(location.href);表示完整的URL。
    console.log(location.search);表示URL中?后的部分,用于指定页面的查询参数。
    str = str.substr(num + 1);
    let arr = str.split("&");
    console.log(arr)
    let token = '';
    let orgId = '';
    let orgName = '';
    for (let i = 0; i < arr.length; i++) {
        num = arr[i].indexOf("=");
        if (num > 0) {
            name = arr[i].substring(0, num);
            value = arr[i].substr(num + 1);
            if (name == 'token') {
                token = value;
            }
            if (name == 'orgId') {
                orgId = value;
            }
            if (name == 'orgName') {
                orgName = value;
            }
        }
    }
    return { token: token, orgId: orgId, orgName: orgName, hostname: location.hostname };
}

7.  Http 请求状态码 、Http协议 和 Https协议 两者的区别 ? 

(1).接口返回的状态码
常见的 HTTP 请求状态码如下: 
| 状态码 | 说明 | 
|---|---| 
| 200 | OK |      请求成功
| 301/302/303 | (网站搬家了,跳转)重定向
| 400 | Bad Request |     请求失败   一般是参数错误
| 401 | Unauthorized |     没有权限  token的问题
| 403 | Forbidden |       不允许请求
| 404 | Not Found |        没有找到接口  路径之类问题     
| 500 | Internal Server Error |   服务器错误
| 503 | Service Unavailable |     服务不可用
 (2).Http 和 Https 两者的区别     (都是用于在网络上传输数据的协议)

        - **安全性**:HTTP 是明文协议,数据在传输过程中不加密,因此容易被窃听和篡改。HTTPS 是安全协议,数据在传输过程中会被加密,因此更安全。

        - **可靠性**:HTTP 不提供可靠性保障,如果数据在传输过程中丢失或损坏,服务器不会通知客户端。HTTPS 提供可靠性保障,如果数据在传输过程中丢失或损坏,服务器会通知客户端并重新发送数据。

        - **性能**:HTTP 的性能比 HTTPS 更高,因为 HTTPS 需要额外的加密和解密操作。

在大多数情况下,建议使用 HTTPS 协议,因为它更安全和可靠。只有在不需要安全性的情况下,才使用 HTTP 协议。

8. 浏览器从输入到页面加载完成发生了什么  与 TCP连接过程三次握手四次挥手

  (1). 浏览器(客户端)输入URL到页面加载完成的步骤

1. 向 DNS 服务器发送请求,( 浏览器解析URL 确定要访问的Web服务器(如www.baidu.***) 和 文件名如(/dir/file.html)发送给DNS服务器

,DNS 服务器会将 URL 解析为 IP 地址 并返回给浏览器)以获取该 URL 对应的 IP 地址

在进行第二步之前 (1.浏览器会查看是否有缓存资源,如果有,直接渲染2.否则 会建立TCP连接 这时候执行 三次握手 过程

2. 向该 IP 地址发送 HTTP 获取该页面的内容 请求。

以下则为 现代服务端渲染过程(同构

3. 获取到服务器发来的 HTML 解析 HTML 代码,并将其转换为 DOM 树。

4. 下载页面中的图片、样式表和 JavaScript 文件。

5. 执行 JavaScript 代码,并更新、渲染 DOM 树。

整个过程大约需要 100 毫秒左右。

这时候执行 四次挥手 过程

(2).三次握手、四次挥手过程 

 SYN 确认包   ACK同步包   FIN终止包

SYN、ACK、FIN 状态:1有效,0无效   (也就是说SYN为1,则表示确认请求,FIN为1,则表示确认终止)发送ACK=1表示 把SYN的状态和ACK状态同步,ACK可以理解为完成的提示

        三次握手如下:客户端向服务器发送确认包,服务器向客户端发送 确认 + 操作完毕 包,客户端向服务器发送 收到

1. 客户端向服务器发送 SYN 包,SYN 包中包含客户端的 IP 地址和端口号。

2. 服务器向客户端发送 SYN+ACK 包,SYN+ACK 包中包含服务器的 IP 地址和端口号。

3. 客户端向服务器发送 ACK 包,ACK 包中包含客户端的 IP 地址和端口号。

        四次挥手如下:客户端向服务器发送终止请求,服务器终止后,向客户端发送终止请求,客户端终止

1. 客户端向服务器发送 FIN 包,FIN 包中包含客户端的 IP 地址和端口号。

2. 服务器向客户端发送 ACK 包,ACK 包中包含服务器的 IP 地址和端口号。

3. 服务器向客户端发送 FIN 包,FIN 包中包含服务器的 IP 地址和端口号。

4. 客户端向服务器发送 ACK 包,ACK 包中包含客户端的 IP 地址和端口号。 

8.  rem、vw/vh 单位适配 设置

> > > 1. (手动设置 rem) 1rem 相当于根元素 html 上的 font-size 的值 所以设置 rem 的换算的话可以用 vw,vw 相当于把屏幕的宽度分成了 100 份,也就是说 1 个 vw 相当于 iphone6 的宽度 375px 除以 100 等于 3.75px,所以 100px 相当于 100px 除以 3.75px 大约为 26.666666666666668 个 vw,所以这样就实现了 rem 适配

```css
html {
  font-size: 26.666666666666668vw !important;
}

.box {
  width: 1rem;
  height: 1rem;
  background-color: red;
}
```

[vant 官网](https://vant-contrib.gitee.io/vant/#/zh-***/toast)

> > > 2. <1>.安装命令 npm install vant@next --save (因为使用的是 vue3.x 所以要安装 vant 的针对 vue3 的版本 vant@next).
> > >    插件 rem 适配 vant 的进阶使用 通过 `npm install postcss postcss-pxtorem --save-dev `(用来将 px 为 rem 尺寸)配置 rem 的根元素字体大小 安装适配插件 `lib-flexiable` 安装命令 `npm i amfe-flexible --save` 注意这里会报错 `postCss` 需要 8 的版本 所以这里建议对` postcss-pxtorem` 进行降级

降级命令` npm install postcss-pxtorem@5.1.1 --save`
<2>.在 main.js 中引入 vant 组件库

```ts
 import vant from 'vant'
 createApp(App).use(vant).$mount('#app)
```

<3>.在 main.js 中导入配置根节点字体大小的方法

```ts
import "amfe-flexible";
```

<4>.接下来在项目根目录中新建一个 postcss.config.js 写入以下代码

```ts
// postcss.config.js
module.exports = {
  plugins: {
    // postcss-pxtorem 插件的版本需要 >= 5.0.0
    "postcss-pxtorem": {
      rootValue({ file }) {
        // 判断是否是vant的文件 如果是就使用 37.5为根节点字体大小
        // 否则使用75 因为vant使用的设计标准为375 但是市场现在的主流设置尺寸是750
        return file.indexOf("vant") !== -1 ? 37.5 : 75;
      },
      // 配置哪些文件中的尺寸需要转化为rem *表示所有的都要转化
      propList: ["*"],
    },
  },
};
```

9.  Vue 过渡动画

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

  3. v-enter-to2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

  4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

  6. v-leave-to2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

<div id="example-1">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>

<style>

/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}
</style>

你也可以定制进入和移出的持续时间

1
转载请说明出处内容投诉
CSS教程_站长资源网 » 前端(JS到Vue)-面试前必刷题

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买