前端面试

142 阅读20分钟

一、七层网络模型

应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

  1. 物理层(Physical Layer) 这是最底层,就像餐厅里的桌椅、餐具等物理设施。它负责实际的数据传输媒介和信号,比如电缆、光纤或无线电波。在这个层次上,我们只关心如何通过物理连接传输原始比特流(0和1)。

  2. 数据链路层(Data Link Layer) 这一层就像是服务员与你之间的直接互动。服务员需要确保你的订单准确无误地传达给厨房,并且当菜品准备好后,再准确无误地送到你的桌上。数据链路层负责在同一网络内的设备间进行可靠的数据帧传递,并处理错误检测和纠正。

  3. 网络层(Network Layer) 想象一下你点的食材是从远处的农场运来的,涉及到不同地点之间的物流运输。网络层就类似于这个物流系统,负责将数据从源地址路由到目的地址。这包括选择最佳路径以及处理跨多个网络的数据包转发。

  4. 传输层(Transport Layer) 回到餐厅,考虑厨师准备你的食物的过程。厨师要保证每道菜都按照菜单的要求制作,并检查质量。传输层确保数据完整地从发送方传输到接收方,提供端到端的通信服务。它还负责流量控制和错误恢复。

  5. 会话层(Session Layer) 这就好比是你在餐厅里与朋友建立的一段对话或者交流。会话层管理设备间的对话,例如开启、维持和关闭应用程序之间的通信会话。

  6. 表示层(Presentation Layer) 当你点的食物被端上来时,它的呈现方式(如摆盘、装饰等)就是表示层的工作。这一层负责数据格式转换、加密解密以及压缩解压,确保信息能够以一种可读的形式被对方理解。

  7. 应用层(Application Layer) 最后,应用层就像是你最终享用美食的过程。它是用户直接交互的界面,提供了各种网络服务和协议,比如HTTP用于浏览网页,SMTP用于发送邮件等。

通过这样的比喻,希望你能更容易地理解七层网络模型中每个层次的功能和它们之间是如何协同工作的。每一层都有其特定的角色,在确保信息能从一端顺利传达到另一端的过程中发挥着不可或缺的作用。

二、TCP三次握手四次挥手

当然可以!让我们用更通俗的方式来解释TCP的三次握手和四次挥手。

三次握手

想象一下你在打电话给朋友:

  1. 第一次握手:你拨打了朋友的电话,说:“嘿,能听到我说话吗?”这相当于客户端发送一个SYN包给服务器,表示希望建立连接。
  2. 第二次握手:你的朋友接听了电话,并且回答:“能听到!”然后又问:“你能听到我说话吗?” 这就像是服务器回复了一个ACK包确认收到了客户端的消息,同时也发送一个SYN包请求同步序列号。
  3. 第三次握手:你说:“能听到!”这样双方都确认了彼此能够听到对方的声音。这就像是客户端发送一个ACK包确认收到了服务器的SYN包。现在你们俩都知道对方听得到自己说话了,就可以开始聊天了(数据传输)。

通过这三个步骤,双方确保了他们可以互相通信,从而建立了连接。

四次挥手

现在假设你们聊完了,准备挂断电话:

  1. 第一次挥手:你先说:“我这边说完了,等你说完咱们就挂电话吧。” 这就像主动关闭方发送一个FIN包告诉对方它已经没有数据要发送了,但还可以接收数据。
  2. 第二次挥手:你的朋友回应:“好的,我知道了。” 这是被动关闭方发送一个ACK包确认收到了你的FIN包。此时,你不会再发送新的信息,但还在等待朋友说完他的话。
  3. 第三次挥手:朋友接着说:“我也说完了,我们可以挂电话了。” 这就是被动关闭方发送一个FIN包给主动关闭方,表示它也没有更多的数据要发送了。
  4. 第四次挥手:你说:“好的,再见!” 然后稍微等了一会儿才真正挂断电话,以确保朋友真的听到了你说的“再见”。这是主动关闭方发送最后一个ACK包进行确认,并且进入一个短暂的等待期(TIME_WAIT),以确保对方确实收到了这个确认消息。

经过这四个步骤,双方都确认不再需要发送或接收数据,于是连接被安全地关闭。

总结

  • 三次握手就像是在确保双方都能清楚地听到对方说话,以便开始有效的沟通。
  • 四次挥手则是确保双方都有机会说完自己的话,并且确认对方知道通话结束了,之后再正式挂断电话。

这种方式保证了通信的安全性和可靠性。

三、什么是闭包?(摘自阮一峰老师)

闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

理解闭包,首先必须理解变量作用域。JavaScript(ES5) 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。

var n = 999;

function f1() {
  console.log(n);
}
f1() // 999

上面代码中,函数f1可以读取全局变量n

但是,正常情况下,函数外部无法读取函数内部声明的变量。

function f1() {
  var n = 999;
}

console.log(n)
// Uncaught ReferenceError: n is not defined(

上面代码中,函数f1内部声明的变量n,函数外是无法读取的。

如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。

function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}

上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。

闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。

为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc)用到了外层变量(start),导致外层函数(createIncrementor)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。

闭包的另一个用处,是封装对象的私有属性和私有方法。

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25

上面代码中,函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

四、什么是作用域?

作用域指的是程序中一个标识符(变量、函数名等)的可访问性或可见性范围。 常见的作用域类型包括:

  • 全局作用域:在任何地方都可以访问的变量。
  • 函数作用域:在函数内部声明的变量,只能在该函数内部访问。
  • 块级作用域:在一些语言中(比如ES6之后的JavaScript),在{}块内声明的变量只能在该块内访问。

五、判断数据类型?

1.判断所有数据类型Object.prototype.toString.call();

Object.prototype.toString.call(1) // '[object Number]' 数组
Object.prototype.toString.call('1') // '[object String]' 字符串
Object.prototype.toString.call(true) // '[object Boolean]' 布尔
Object.prototype.toString.call(undefined) // '[object Undefined]' undefined
Object.prototype.toString.call(Symbol()) // '[object Symbol]' Symbol
Object.prototype.toString.call([]) // '[object Array]' 数组
Object.prototype.toString.call({}) // '[object Object]' 对象
Object.prototype.toString.call(null) // '[object Null]' null
Object.prototype.toString.call(/a/) // '[object RegExp]' 正则

2. Array 判断数组

Array.isArray('') // false
Array.isArray([]) // true

  1. typeof
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof true // 'boolean'
typeof (()=>{}) // 'function'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof /a/ // 'object'

六、vue2和vue3的区别?

  1. 性能提升
  • 更快的渲染速度:Vue3 使用了基于 Proxy 的响应式系统,相比 Vue 2 的 Object.defineProperty,性能更高。 - 更小的包体积:通过 Tree-shaking 支持,Vue3 的核心代码体积减少了约 40%。
  • 更好的编译优化:引入了编译时优化(如静态节点提升、补丁标志等),减少了运行时开销
  1. Composition API

    Vue 3 引入了新的 Composition API,作为Options API(Vue 2中使用的API)的补充。Composition API提供了一种更加灵活的方式来组织和重用代码逻辑,尤其适合处理复杂组件。

  2. 编译时优化

  • PatchFlag:Vue 3 的编译器会在静态内容和动态内容之间进行区分,并为动态节点标记 PatchFlag。这样在更新时只会重新渲染带有 PatchFlag 的部分,而不是整个组件树。
  • Hoist Static:静态节点会被提升到 render 函数之外,在每次渲染时复用这些静态节点,减少了不必要的计算。
  • Cache Handlers:事件处理器可以被缓存起来,避免在每次渲染时重新创建函数实例。
  1. 更好的TypeScript支持

    Vue 3 对 TypeScript 的支持更加原生和友好,提供了更好的类型推断和类型检查功能,而Vue 2则需要额外的工具和配置来获得类似的支持。

  2. 改进的响应式系统

    Vue 3 使用了基于Proxy的对象来实现其响应式系统,相比Vue 2中的Object.defineProperty方法,提供了更好的性能和更多的特性,比如对数组元素和对象属性的监听能力更强。

  3. 生命周期钩子

    Vue 3 调整了一些生命周期钩子的命名规则,例如beforeDestroy被替换为onBeforeUnmountdestroyed被替换为onUnmounted等。

  4. Fragments

    Vue 3 支持片段(Fragments),这意味着组件可以有多个根节点,而在Vue 2中,每个组件必须有一个单独的根节点。

  5. Teleport(传送门)

    Vue 3 新增了一个名为Teleport的功能,允许开发者将组件的一部分渲染到DOM中的任意位置,这对于模态框、提示框等场景非常有用。

  6. Suspense: 可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态

  7. 移除或调整了一些特性

  • 移除了 on、on、off 和 $once 等事件 API。
  • 移除了 filter 过滤器,推荐使用计算属性或方法替代。
  • v-bind 的 .sync 修饰符被移除,改用 v-model。

七. call apply bind

都是用来重定义 this 这个对象的

image.png obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海

obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海

obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海

obj.myFun.bind(db,['成都','上海'])(); // 德玛 年龄 99 来自 成都, 上海去往undefined

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了

  • call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'成都', ... ,'string' )。
  • apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['成都', ..., 'string' ])。
  • bind 除了返回是函数以外,它的参数和 call 一样。

八. 项目优化

  • sdn常用依赖
  • js分包
  • 首页骨架屏
  • zip压缩
  • 组件懒加载
  • 缓存策略

九. 原型链

JavaScript中的原型链是一种机制,它允许对象之间共享属性和方法。在JavaScript中,每个对象都有一个指向另一个对象(即它的原型)的内部链接。这个原型对象自身也有自己的原型,如此层层递进,直到最终到达null,形成一条链,这就是所谓的“原型链”。

当访问一个对象的属性或方法时,如果该对象自身没有定义此属性或方法,则JavaScript引擎会通过原型链向上查找,直到找到该属性/方法或者达到原型链的末端(null)。这种机制使得JavaScript可以实现继承。

原型链的基本概念

  1. 构造函数:用于创建特定类型的对象。例如,Person可以是一个构造函数,用于创建人的实例。
  2. 原型(prototype):每一个构造函数都有一个prototype属性,它指向该构造函数的原型对象。所有由该构造函数创建的对象都会继承这个原型对象的属性和方法。
  3. proto:这是每个对象都有的一个属性,它指向创建该对象的构造函数的prototype。通过这个属性,对象间形成了原型链。

示例说明

// 定义一个构造函数
function Person(name) {
    this.name = name;
}

// 给Person的原型添加一个方法
Person.prototype.sayHello = function() {
    console.log("Hello, my name is " + this.name);
};

// 创建两个Person的实例
let person1 = new Person("Alice");
let person2 = new Person("Bob");

person1.sayHello(); // 输出: Hello, my name is Alice
person2.sayHello(); // 输出: Hello, my name is Bob

// 证明原型链的存在
console.log(person1.__proto__ === Person.prototype); // 输出: true
console.log(Person.prototype.__proto__ === Object.prototype); // 输出: true
console.log(Object.prototype.__proto__); // 输出: null,表示原型链的终点

在这个例子中,Person.prototypeperson1person2的原型,而Object.prototype又是Person.prototype的原型,这样就形成了一个原型链。当我们调用person1.sayHello()时,实际上是在其原型Person.prototype上找到了这个方法并执行。如果Person.prototype上找不到需要的方法或属性,JavaScript引擎将继续沿着原型链向上查找,直到找到目标或者到达原型链的末尾(null)。

js继承

在JavaScript中,实现继承主要通过原型链(prototype chaining)的方式。以下是几种常见的继承模式及其示例:

3. 原型链加构造函数继承(组合继承)

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function() {
    console.log('Hello, I am ' + this.name);
};

function Child(name) {
    Parent.call(this, name); // 第二次调用Parent
}

// 继承方法
Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;

let child = new Child('Child');
child.sayHello(); // 输出: Hello, I am Child

组合继承虽然强大,但是由于两次调用父类构造函数,效率不是很高。

4. 原型式继承

利用一个已有的对象创建一个新的对象,新对象可以共享原有对象的属性。

function createObject(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

let parent = {
    name: 'Parent',
    sayHello: function() {
        console.log('Hello, I am ' + this.name);
    }
};

let child = createObject(parent);
child.name = 'Child';
child.sayHello(); // 输出: Hello, I am Child

5. 寄生式继承

在原型式继承的基础上,增强对象,返回构造函数。

function createObject(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function createChild(parent) {
    let child = createObject(parent);
    child.sayGoodbye = function() {
        console.log('Goodbye');
    };
    return child;
}

let parent = {
    name: 'Parent',
    sayHello: function() {
        console.log('Hello, I am ' + this.name);
    }
};

let child = createChild(parent);
child.sayGoodbye(); // 输出: Goodbye

6. ES6中的class关键字

ES6引入了class关键字简化了继承语法,但本质上还是基于原型链。

class Parent {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(`Hello, I am ${this.name}`);
    }
}

class Child extends Parent {
    constructor(name) {
        super(name); // 调用父类的构造函数
    }
}

let child = new Child('Child');
child.sayHello(); // 输出: Hello, I am Child

以上就是在JavaScript中实现继承的一些常见方式。每种方式都有其适用场景和局限性,开发者可以根据实际需要选择最合适的继承模式。

十一 数组去重

在JavaScript中,有多种方法可以对数组进行去重。以下是几种常见的实现方式:

  1. 使用Set(ES6新增): Set 对象是一种新的数据结构,它只能存储唯一的值(重复的值会被忽略),这使得它非常适合用来去除数组中的重复项。

    let array = [1, 2, 3, 4, 4, 5, 5, 6];
    let uniqueArray = [...new Set(array)];
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5, 6]
    
  2. 使用filter和indexOf: 这种方法利用了filter函数来遍历数组,并结合indexOf检查是否是首次出现的元素。

    let array = [1, 2, 3, 4, 4, 5, 5, 6];
    let uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5, 6]
    
  3. 使用reduce: reduce 方法可以接受一个回调函数,用于为数组中的每个元素执行一次,并累计结果。通过这种方式也可以实现数组去重。

    let array = [1, 2, 3, 4, 4, 5, 5, 6];
    let uniqueArray = array.reduce((unique, item) => {
        return unique.includes(item) ? unique : [...unique, item];
    }, []);
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5, 6]
    

每种方法都有其特点,可以根据具体的使用场景选择最适合的方法。使用Set是最简洁且性能较高的方法,特别是在处理大型数组时。其他两种方法虽然稍微复杂一些,但它们提供了更多的灵活性,例如可以在去重的同时根据特定逻辑过滤元素。

十二 css居中

方案一 flex布局

.parent{
    width: 500px;
    height: 500px;
    display: flex;
    background: pink;
}
.children{
    width: 100px;
    height: 100px;
    background: indianred;
    margin: auto;
}

方案二 flex加margin:auto

.parent{
    width: 500px;
    height: 500px;
    display: flex;
    background: pink;
    justify-content: center;
    align-items: center;
}
.children{
    width: 100px;
    height: 100px;
    background: indianred;
}

方案三 定位

.parent {
    position: relative;
    width: 100%;
    height: 100%;
    background: pink;
}
.children {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    background: rgba(196, 7, 38, 0.514);
}

方案四 表格布局

.parent {
    width: 500px;
    height: 500px;
    display: table-cell;
    text-align: center;
    vertical-align: middle;
    background: pink;
}
.children {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: rgba(196, 7, 38, 0.514);
}

方案五 网格布局

.parent {
    width: 500px;
    height: 500px;
    display: grid;
    background: pink;
}
.children {
    align-self: center;
    justify-self: center;
    width: 100px;
    height: 100px;
    background: rgba(196, 7, 38, 0.514);
}

方案六 定位加2D转换

 .parent {
    width: 400px;
    height: 400px;
    background: pink;
    position: relative;
}
.children {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    width: 100px;
    height: 100px;
    background: rgba(196, 7, 38, 0.514);
}

方案七 定位加margin

.parent {
      width: 500px;
      height: 500px;
      background-color: red;
      position: relative;
    }
    .children {
      position: absolute;
      width: 100px;
      height: 100px;
      left: 50%;
      top: 50%;
      margin: -50px;
      background-color: blue;
    }

方案八 使用display: inline-block; + vertical-align: middle; 设置基线位置

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .wrapper {
        width: 500px;
        height: 500px;
        background-color: pink;
        text-align: center;
      }
      .box {
        width: 100px;
        height: 100px;
        background-color: deepskyblue;
        display: inline-block;
        vertical-align: middle;
      }
      .help {
        width: 0;
        height: 100%;
        display: inline-block;
        vertical-align: middle;
      }
    </style>
  </head>
  <body>
    <div class="wrapper">
      <div class="box"></div>
      <div class="help"></div>
    </div>
  </body>
</html>

十二 vue3新特性

最主要的区别就是 vue3 Composition API(组合式) 而vue2是Options API(选项式)

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

function update(event) {
  x.value = event.pageX
  y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

新增两个内置组件 Teleport Suspense

<Teleport> 接收一个 to prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。

<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>

在初始渲染时,<Suspense> 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense> 会进入完成状态,并将展示出默认插槽的内容。 有了 <Suspense> 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。

<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <Dashboard />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

Vue.prototype 替换为 config.globalProperties

Vue.prototype.$axios = axios // vue2 将axios挂载到全局
app.config.globalProperties.$axios = axios //vue3 将axios挂载到全局

style中使用变量

<template>
  <div class="content">
    <p>这是一个示例组件。</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      theme: {
        primaryColor: '#ff0000'
      }
    }
  }
}
</script>

<style scoped>
.content {
  width: 500px;
  height: 500px;
  text-align: center;
  vertical-align: middle;
  --primary-color: v-bind('theme.primaryColor'); /* Vue 3.2+ 支持 */
  color: var(--primary-color);
}
</style>

支持 Fragments多个根节点:

<template>
  <header>Header Content</header>
  <main>Main Content</main>
  <footer>Footer Content</footer>
</template>

响应式原理的变化 defineProperty 换成 proxy

//使用Object.defineProperty

let obj = {};
Object.defineProperty(obj, 'name', {
    value: 'Qwen',
    writable: false, // 不可修改
    configurable: false, // 不能删除
    enumerable: true // 可以枚举
});
console.log(obj.name); // 输出 "Qwen"
obj.name = 'NewName'; // 如果在严格模式下会抛出TypeError错误
console.log(obj.name); // 依然输出 "Qwen" 因为属性不可写

//使用proxy
let user = {
    name: "John"
};

let handler = {
    set: function(target, key, value) {
        if (key === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('Age must be an integer.');
            }
            if (value < 0 || value >= 150) {
                throw new RangeError('Age must be between 0 and 150.');
            }
        }
        target[key] = value;
        return true;
    }
};

let proxyUser = new Proxy(user, handler);

proxyUser.age = 30; // 正常工作
console.log(proxyUser.age); // 输出: 30
proxyUser.age = 'young'; // 抛出TypeError

生命周期钩子

Vue2--------------vue3
beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated

十三 get post请求大小限制

get 由于Internet Explorer的URL最大长度约为2048字符,而其他现代浏览器如Chrome、Firefox等支持更长的URL,但仍然有限制,所以最大不超过2k

post 理论上,POST请求的数据量没有明确的上限,但实际上它受到服务器配置和网络协议的限制。例如,Apache服务器可以通过修改LimitRequestBody指令来设置上传文件的最大尺寸;Nginx也有类似的配置项client_max_body_size。此外,客户端与服务器之间的网络条件也可能影响实际能传输的数据大小

十四 父子组件数据双向绑定

父子组件双向绑定 v-model和prop.sync使用,vue2.2版本后推荐v-model,prop.sync 只能是单个值, v-model可以是对象,在 Vue.js 中,.sync 修饰符是一种简化父组件与子组件之间双向绑定的机制。它提供了一种更简洁的方式来实现父组件和子组件之间的数据同步,避免了手动触发事件来更新父组件的数据。

.sync 修饰符的基本用法

假设我们有一个父组件和一个子组件,父组件需要将某个属性传递给子组件,并且希望在子组件中修改这个属性时,父组件中的值也能自动更新。

示例:不使用 .sync

父组件

<template>
<div>
<p>Parent Value: {{ parentValue }}</p>
<ChildComponent :value="parentValue" @update:value="updateValue" />
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: { ChildComponent },
data() {
    return {
        parentValue: 'Initial Value'
    };
},
methods: {
    updateValue(newValue) {
        this.parentValue = newValue;
    }}
};
</script>

子组件

<template>
<div>
<p>Child Value: {{ value }}</p>
<button @click="updateValue('New Value')">Update Value</button>
</div>
</template>

<script>
export default {
props: ['value'],
methods: {
    updateValue(newValue) {
        this.$emit('update:value', newValue);
    }}
};
</script>

在这个示例中,子组件通过 $emit 触发 update:value 事件来通知父组件更新 value 属性。虽然这种方法可以实现双向绑定,但代码显得有些冗长。

使用 .sync 修饰符简化代码

父组件

<template>
<div>
<p>Parent Value: {{ parentValue }}</p>
<ChildComponent :value.sync="parentValue" />
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    components: { ChildComponent },
    data() {
        return {
            parentValue: 'Initial Value'
        };
    }
};
</script>

子组件

<template>
<div>
<p>Child Value: {{ value }}</p>
<button @click="updateValue('New Value')">Update Value</button>
</div>
</template>

<script>
export default {
    props: ['value'],
    methods: {
        updateValue(newValue) {
            this.$emit('update:value', newValue);
        }
    }
};
</script>

在这个示例中,父组件使用 .sync 修饰符简化了对 value 属性的双向绑定。子组件仍然通过 $emit('update:value', newValue) 来通知父组件更新值,但父组件不需要显式地监听事件并更新状态。

.sync 修饰符的工作原理

.sync 修饰符实际上是 Vue 提供的一种语法糖,它会自动展开为一个事件监听器。具体来说:

  • 在父组件中,<ChildComponent :value.sync="parentValue" /> 会被展开为 <ChildComponent :value="parentValue" @update:value="parentValue = $event" />
  • 子组件通过 $emit('update:value', newValue) 触发事件,父组件接收到事件后更新 parentValue 的值。

.sync 修饰符的局限性

尽管 .sync 修饰符提供了便捷的双向绑定方式,但它也有一些局限性:

  1. 仅适用于单个属性.sync 只能用于单个属性的双向绑定,不能直接用于对象或数组的深层嵌套属性。
  2. 不支持多个属性同步:如果你需要同步多个属性,必须分别为每个属性使用 .sync 修饰符。
  3. 命名约定:子组件必须使用特定的事件名称(即 update:propName)来触发更新,这限制了灵活性。

替代方案:使用 v-model

从 Vue 2.2.0 开始,v-model 支持自定义事件名称,可以用来替代 .sync 修饰符,特别是在需要同步多个属性的情况下。

使用 v-model 替代 .sync

父组件

<template>
<div>
<p>Parent Value: {{ parentValue }}</p>
<ChildComponent v-model="parentValue" />
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    components: { ChildComponent },
    data() {
        return {
            parentValue: 'Initial Value'
        };
    }
};
</script>

子组件

<template>
<div>
<p>Child Value: {{ value }}</p>
<button @click="updateValue('New Value')">Update Value</button>
</div>
</template>

<script>
export default {
    props: ['value'],
    methods: {
        updateValue(newValue) {
            this.$emit('input', newValue); // 使用 'input' 事件
        }
    }
};
</script>

在这个示例中,父组件使用 v-model 绑定 parentValue,子组件通过 $emit('input', newValue) 触发更新。这种方式同样简洁,并且更加灵活。

总结

.sync 修饰符是 Vue.js 中一种简化父组件与子组件之间双向绑定的机制,特别适用于需要简单同步单个属性的场景。它的主要优点是减少了样板代码,使代码更加简洁易读。然而,对于需要同步多个属性或更复杂的双向绑定需求,建议使用 v-model 或其他更灵活的方式。

希望这些信息能帮助您更好地理解和使用 .sync 修饰符!如果有更多问题或需要进一步的帮助,请随时告知。

十五 ES6常用新特性

let、const、解构赋值、扩展运算符、默认参数、模板字符串、箭头函数、Promise、async/await、export/import

Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

Object.assign(target, source1, source2)

Object.assign是浅拷贝;

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.is()

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

Object.keys(obj)

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values(obj)

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries(obj)

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

class

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已

// ES5

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

//ES6
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

for...of

for...in循环读取键名,for...of循环读取键值; for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样

let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}