Skip to content

前端知识体系

HTML和CSS

1.如何理解HTML语义化?

所谓的HTML语义化就是使用合理的标签布局网页,例如H1就是网页的一级标题,em表示强调,i是斜体字,定义强调文本等。这样做有以下好处.

  1. 可以提高代码的可读性。
  2. 有利于seo优化(可以提高搜索引擎的排名,获取自然流量)

2.DOCTYPE(⽂档类型)的作⽤

DOCTYPE是HTML的文档类型声明,它会告诉浏览器以哪种文档类型(HTML/XHTML)解析文档,不同的渲染模式会影响浏览器对css和js代码的解析,它必须声明 代码的第一行。
浏览器的渲染模式:

  1. CSS1Compat:标准模式(Strick mode),默认模式,浏览器使用W3C的标准解析渲染页面。在标准模式中,浏览器以其支持的最高标准呈现页面。
  2. BackCompat:怪异模式(混杂模式)(Quick mode),浏览器使用自己的怪异模式解析渲染页面。在怪异模式中,页面以一种比较宽松的向后兼容的方式显示。

3.常⽤的meta标签有哪些

charset: 用来描述HTML的文档编码类型 <meta charset="UTF-8" >
keywords: 页面关键词
<meta name="keywords" content="关键词" /> description: 页面的描述
<meta name="description" content="页面描述内容" />
viewport,适配移动端,可以控制视口的大小和比例
<meta http-equiv="refresh" content="0;url=" />
refresh,页面重定向和刷新
<meta http-equiv="refresh" content="0;url=" />

4.script标签中defer和async的区别

defer和async都是异步的方式加载外部的js脚本,他们都不会阻塞页面的解析,唯一的区别就是多个 defer属性的脚本按照加载顺序执行,多个async的js脚本都是并行的,不能保证加载顺序s

5.什么是块元素和行内元素?

块元素的特点是单独占一行,块元素可以包含行内元素
例如: p table ul ol h1~h6
行内元素的特点是不会单独占一行,每个标签会依次排列,知道超出容器才换行,行内元素不能包含块元素。
例如:span img input button

6.什么是盒模型?

盒模型本质上就是一个标签所占空间的具体划分,一个完整的盒模型是有外边距,边框,内边距和内容区四部分组成。
盒模型分为标准盒模型和IE盒模型,标准盒模型的宽度只包含内容区的宽和高。IE盒模型的宽度是由内容区,内边距和边框组成的。

7.外边距重叠问题

当父子元素的子元素设置一个margin时,父元素也会有margin,这就是所谓的外边距重叠。
当兄弟元素上面的元素存在margin-bottom并且下面元素存在margin-top的情况下,两个元素的间距哪个值大用哪个。

具体看下列的例子:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    body{
        background: #000;
    }
    .content{
        width: 400px;
        height: 400px;
        background: #bfa;
    }       

    .item{
        width: 200px;
        height: 200px;
        background: red;
        margin-top: 20px;
    }
</style>
<body>
    <div class="content">
        <div class="item">
            111
        </div>
    </div>
</body>
</html>

具体效果如下: img

如何解决?

  1. 让父元素开启bfc,给父元素添加overflow: hidden;或者overflow: auto;
  2. 给父元素添加一个边框

8. margin负值的问题

margin-left 自身向左移动
margin-right 自身不动,右侧元素向左移动
margin-top 自身向上移动
margin-bottom 自身不动,下边的元素向上移动

9.什么是BFC?

BFC是块级格式化上下文,会形成一个独立的渲染区域,内部元素不会影响外部元素的布局。 形成条件:

  1. float不是none
  2. position是absolute或者fixed
  3. overflow不是visible
  4. display是flex或者inline-block
    应用: 清楚浮动:overflow: hidden;触发bfc

10. 高度塌陷是什么? 如何解决?

当父子元素中父元素的宽高由子元素的宽高撑起来时,子元素浮动,会造成父元素的高度为0。
通常我们会使用如下代码给父元素添加同时解决高度塌陷和边距重叠问题

css
.clearfix{
  content:'';
  display:table;
  clear:both;
}

js高级

1. 深入理解作用域

作用域主要分为3个方面:

  1. 全局作用域

生命周期:在页面创建时产生,在页面关闭时销毁 全局作用域中的变量在页面的任何位置都能访问

  1. 函数作用域

生命周期: 函数调用时创建,执行完毕销毁 在函数内部声明的变量只能在函数内部访问

  1. 块级作用域

通过let 和const 声明会形成块级作用域,外部无法访问

2.作用域链

作用域的用途,是保证对执行环境中变量和函数的有序访问

js
var a = 1
function fn() {
    var b = a + 1
    console.log(b)
}
fn()

在代码执行过程中,在函数内部找不到a变量,那么会到全局作用域中寻找,所以b = 2。
变量在寻找值的过程中会优先在内部作用域中寻找,如果找不到的话,会向外部的作用域中寻找。

3.JS的预编译

预编译是上下文创建之后, js代码执行前的一段时期, 在这个时期, 会对js代码进行预处理

预编译分为全局预编译和函数预编译

全局预编译

全局上下文创建 ---> 生成变量对象VO---> 寻找变量(属性名为变量名,属性值为undefined)--->寻找函数(属性值为函数本身)

如果变量名与函数名冲突,那么函数声明会将变量声明覆盖

函数预编译

函数上下文创建,生成变量对象AO ---> 寻找变量声明,值为undefined ---> 寻找形参 值为undefined ---> 将实参赋值给形参,替换AO中的值 ---> 寻找函数,函数名作为属性名,函数本身作为 属性值 ---> 如果函数名与变量名冲突,函数声明会将变量声明覆盖

4.内存空间

计算机存储空间主要分为两个

内存 容量小 运行速度快 程序运行时占用(临时) 硬盘 容量大 运行速度慢 程序安装

js运行和存储空间的关联

js运行在内存中,当数据进行数据持久化时,会占用硬盘

js的哪些场景会占用内存空间?

函数声明 --> 函数体: 页面运行期间持续存在 函数运行 --> 执行上下文: 临时占用内存空间,函数运行结束释放内存

内存的声明周期

分配内存(定义变量) ---> 使用内存(变量的读取或写入) ---> 回收内存(对不再使用的内存进行回收)

js
const a = 1; // 分配内存

console.log(a)  // 使用内存

a = null // 释放内存

js中内存空间类型: 栈内存与堆内存

栈内存与堆内存都是存储空间,栈内存使用的是栈结构,特点是先进后出,后进先出 对内存使用的是堆结构,没有先后顺序,通过指针的模式访问数据

数据类型

基本数据类型 stirng,number,boolean,null,undefined es6中新增两种基本数据类型BigInt,Symbol

特性 按值访问 是不可变数据(值本身无法被改变)

js
var a = 1;
var b = 1;
/** 基本数据类型比较的是值 */
console.log(a === b) // true

b += 1;
console.log(a === b) // false

b += 1;的过程是,栈内存创建一个新的变量,赋值为2,原本的b变量会变为可回收的状态,垃圾回收机制回收原本的b变量

引用数据类型

object array function Date RegExp .....

特性 按引用访问 值可以被改变

js
var a = {
    name: "孙悟空",
    age: 18
}
var b = a

// 引用数据在比较时,本质上是比较对象的地址
console.log(b == a) // true
b.age = 20
console.log(b == a) // true

c = {}
d = {}
// 两个不同的对象,地址不同
console.log(c == d) // false

a和b本质是是一个对象,在堆内存中,引用数据类型的数据是根据指针的模式指向同一个对象。

对象在栈内存中存储对象名和地址,堆内存中存储对象值

对象常见的问题: 值引用

js
// 示例1
var person = {
  province: 'hubei',
  city: 'wuhan'
}

function getPerson(p, name, age) {
  p.name = name; 
  p.age = age;
  return p;
}

var a = getPerson(person, 'Bob', 10);

console.log(a);
console.log(person);
console.log(a === person); // true

// 示例2
var foo = {
  a: 1,
  b: 2
};

var bar = Object.assign(foo, { c: 100 });

console.log(foo, bar);

var bar = Object.assign({}, foo, { c: 100 });

5.浅拷贝与深拷贝

浅拷贝: 浅拷贝是其属性与拷贝源对象的属性共享相同的引用 实现浅拷贝的几种方式 Object.create()

js
var obj = {name:"孙悟空",age:18}
var obj2 = object.create(obj)

object.assign()

js
var obj = {name:"孙悟空",age:18}
var obj2 = object.aassgin({},obj)

扩展运算符

js
var obj = {name:"孙悟空",age:18}
var obj2 = { ...obj }

深拷贝
实现深拷贝的几种方式

  1. JSON.parse(JSON.stringfy(obj)) 这个方式有一种缺陷,看下列代码
js
var obj = {
    a: function() {},
    b: Symbol("obj"),
    c: /abc/,
    d: undefined
}

const obj2 = JSON.parse(JSON.stringfy(obj))
console.log(obj2) // { }
  1. lodash 官网地址
  2. 手写深拷贝

深拷贝的原理

  1. 数据类型的划分
  2. 递归处理
  3. 循环引用的处理
js
 function deepclone(object,map = new WeakMap()) {
            // 不需要递归的直接返回
            if (object === null || typeof object !== "object" || typeof object === "function" || object instanceof Date || object instanceof RegExp) {
                return object
            }

            if(map.get(object)) {
                return map.get(object)
            }

            if (object instanceof Map) { // Map结构
                const newObj = new Map()
                map.set(object,newObj)
                for (const element of object) {
                    newObj.set(element[0],deepclone(element[1]),map)
                }
                return newObj
            } else if(object instanceof Set) { // Set结构
                const newObj = new Set()
                map.set(object,newObj)
                for (const element of object) {
                    newObj.add(deepclone(element),map)
                }
                return newObj
            } else if(object instanceof Array) { // Array结构
                const newObj = []
                map.set(object,newObj)
                object.forEach(function(item) {
                    newObj.push(deepclone(item),map)
                })
                return newObj
            } else if (object instanceof Object) { // Object结构
                const newObj = {}
                map.set(object,newObj)
                for (const key in object) {
                    newObj[key] = deepclone(object[key],map)
                }
                return newObj
            }
        }

        const obj = {
            a:1,
            b: {
                c:5
            },
            d: [1,2,3],
            e: /edfsdsd/,
            f: 2024-5-12
        }

        const obj2 = deepclone(obj)
        console.log(obj)
        console.log(obj2)



        const obj4 = {}
        obj4.a = obj4
        var obj8 = deepclone(obj4)
        console.log(obj8)
        obj8.a.b = 1
        console.log(obj4)

6.闭包

概念: 能够访问到其他函数作用域中的对象的函数,成为闭包

js
function fn() {
    var a = 1
    return () => {
        console.log(a)
    }
}

var fn1 = fn()
fn1()

7.this

基本概念

  1. this不能在执行期间被赋值
  2. js中的this它不是固定不变的,是随着它执行环境的变化而改变

this的指向问题

  1. 在一般函数中,this指向window
  2. 在构造函数中,this指向实例对象
  3. 对象调用时,this指向实例对象
  4. call和apply调用时,this指向第一个调用的对象

8.call,apply,bind的使用

call,apply,bind都是用来改变this指向的

call的基本使用

js
const obj = {
            fn() {
                console.log(this)
            }
        }

        const obj2 = {
            name: "obj2"
        }

        obj.fn.call(obj2) // {name: "obj2"}

apply的应用场景

js
const arr1 = ["a","b"]
        const arr2 = [1,2,3]

        // es6的写法
        arr1.push(...arr2)
        console.log(arr1)
        // es5的写法
        arr1.apply(arr1,arr2)
        console.log(arr2)

bind的基本使用

js
const obj = {
    fn() {
        console.log(this)
    }
}

const fn2 = obj.fn
fn2() // this指向window

const fn3 = obj.fn.bind(obj)
fn3() // this指向obj

bind的应用(解决定时器的参数问题)

js
function fn(name) {
    console.log(`hello,${name}`)
}
const delayfn = fn.bind(null,"lihua")
setTimeout(name, 2000); // lihua

apply,bind,call的区别

  1. call和apply是可以直接调用的,但是bind的方法不会立即调用,会返回一个新的函数
  2. call和bind的参数是参数列表,apply的参数是参数数组

9.call,apply,bind手写

  • 原理:
  1. 首先,通过 Function.prototype.myCall 将自定义的 myCall 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. 在 myCall 方法内部,首先通过 typeof this !== "function" 判断调用 myCall 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
  4. 接下来,使用 Symbol 创建一个唯一的键 fn,用于将调用 myCall 的函数绑定到上下文对象的新属性上。
  5. 将调用 myCall 的函数赋值给上下文对象的 fn 属性,实现了将函数绑定到上下文对象上的效果。
  6. 调用绑定在上下文对象上的函数,并传入 myCall 方法的其他参数 args。
  7. 将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。
  8. 返回函数调用的结果。

手写call

js
Function.prototype.myCall = function(context,...args) {
            // 判断被调用的是否是函数
            if (typeof this !== 'function') {
                throw new TypeError("被调用的对象必须为函数");
            }
            // 如果传入是context,否则是globalThis
            context = context || globalThis
            // 防止命名冲突,创建唯一的fn
            let fn = Symbol("key")
            context[fn] = this
            // 调用函数拿到结果
            const result = context[fn](...args)
            // 删除添加的函数
            delete context[fn]
            // 返回结果
            return result
        }

        const obj = {
            name: "obj",
            fn() {
                console.log(this.name)
            }
        }

        const obj1 = {
            name: "obj1"
        }

        obj.fn.myCall(obj1)

手写apply 原理:apply的实现思路跟call类似,就是apply传入参数是以数组的形式传入,所以多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符 ... 将传入的参数数组 argsArr 展开

js
Function.prototype.myApply = function(context,argsArr) {
            // 判断被调用的是否是函数
            if (typeof this !== 'function') {
                throw new TypeError("被调用的对象必须为函数");
            }

            if(argsArr && !Array.isArray(argsArr)){
                throw new TypeError("第二个参数必须为数组");
            }

            // 如果传入是context,否则是globalThis
            context = context || globalThis
            // 防止命名冲突,创建唯一的fn
            let fn = Symbol("key")
            context[fn] = this
            // 判断参数是否为数组,是的话传入参数,不是的话直接调用
            const result = Array.isArray(argsArr)? context[fn](...argsArr):context[fn]()
            // 删除添加的函数
            delete context[fn]
            // 返回结果
            return result
        }

        const obj = {
            name: "obj",
            fn() {
                console.log(this.name)
            }
        }

        const obj1 = {
            name: "obj1"
        }

        obj.fn.myApply(obj1)
        obj.fn.apply(obj1)
        const arr = [1,2,3,8,5,9,5,3]

        console.log(Math.max.myApply(null,arr))
        console.log(Math.max.apply(null,arr))

手写bind

原理:

  1. 首先,通过 Function.prototype.myBind 将自定义的 myBind 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. 在 myBind 方法内部,首先通过 typeof this !== "function" 判断调用 myBind 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象;ES11 引入了 globalThis,它是一个统一的全局对象,无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。
  4. 保存原始函数的引用,使用 _this 变量来表示。
  5. 返回一个新的闭包函数 fn 作为绑定函数。这个函数接受任意数量的参数 innerArgs。
  6. 在返回的函数 fn 中,首先判断是否通过 new 关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用 new 关键字调用时,this 的值会指向新创建的实例对象。通过检查 this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用 new _this(...args, ...innerArgs) 来创建新对象。
  7. 如果不是通过 new 调用的,就使用 apply 方法将原始函数 _this 绑定到指定的上下文对象 context 上。这里使用 apply 方法的目的是将参数数组 args.concat(innerArgs) 作为参数传递给原始函数。
js
Function.prototype.Mybind = function(context,...args) {
            if(typeof this !== "function"){
                throw new TypeError("被调用的对象必须为函数");
            }
            context = context || globalThis
            const _this = this
            return function fn(...innerArgs) {
                if (this instanceof fn) {
                    return new _this(...args,...innerArgs) 
                }
                return _this.apply(context, args.concat(args,innerArgs))
            }
        }

        const obj = {
            name:"obj",
            fn() {
                console.log(this.name)
            }
        }

        const obj1 = {
            name:"obj1"
        }

        const fn5 =  obj.fn.Mybind(obj1)
        console.log(fn5)
        new fn5("a","b",1)
        fn5()

10.ajax的实现原理

  1. 创建Ajax对象
  2. 传入请求方式和请求地址
  3. 发送请求
  4. 获取响应数据

11. 同源策略

跨域是浏览器的一种安全机制,浏览器在请求资源时,请求路径必须要与浏览器路径必须满足同源策略
同源是指协议,域名,端口号必须一致

优点: 大大提高了浏览器访问数据的安全性

请求跨域的三种方式

  1. jsonp
    利用script标签的可以跨域的特性,通过动态添加script标签,传递一个函数,拿到响应结果

缺点 请求方式单一,只能是get请求且代码不整洁

js
// 创建script标签
const script = document.createElement("script")
script.src="http:localhost:3000/getData?cb=callback"
// 回调函数
function callback(res) {
    console.log(res)
}
// 将script标签挂在到页面上
document.getElementByTagName("body")[0].appendChild(script)

node后端代码

js
const express = express()
const app = express()
app.get("/getData",(req,res)=> {
    const { cb } = req.query
    res.send(`${cb}`(JSON.stringfy({
        code: 200,
        data: {
            msg: "hello world"
        }
    })))
})
app.listen(3000,() => {
    console.log("服务器已经启动")
})
  1. core跨域资源共享(主流的跨域解决方案)
js
const express = express()
const app = express()
app.all("*",(req,res,next) => {
    res.header("Access-Control-Allow-Origin","*") // 允许所有的请求源
    res.header("Access-Control-Allow-Headers","*") // 允许所有的请求头
    res.header("Access-Control-Allow-Methods","*") // 允许所有的请求方法
})
app.get("/getData",(req,res)=> {
    const { cb } = req.query
    res.send(`${cb}`(JSON.stringfy({
        code: 200,
        data: {
            msg: "hello world"
        }
    })))
})
app.listen(3000,() => {
    console.log("服务器已经启动")
})
  1. Vue脚手架 通过代理服务器的方式请求资源
vue
devServer: {
    proxy: {
        "/api": {
            target: "http://localhost:3000"
        }
    }
}

12.面向对象

两大编程思想:

  1. 面向过程: 分析解决问题的步骤,逐步通过代码实现
  2. 面向对象: 功能封装在功能内部,实现功能通过调用对象方法来实现

个人的理解:

面向过程强调的是按步骤解决问题,而面向对象强调的是封装 面向对象解决了面向过程中全局变量污染的问题

13.面向对象的三大特性

封装 将属性和方法封装在对象内部,不会对外部造成污染

继承 字类可以继承父类的方法和属性

多态 子类继承父类的基础上,可以在字类新增方法或属性

14.封装-工厂模式

特点: 对象创建过程中封装在函数里面,可以更好的封装复杂逻辑 缺点: 创建出来的对象与函数本身没有关系

js
function superCarFactory() {
    var car = new Object()
    car.name = "红旗"
    car.color = "black"
}

superCarFactory()

15.封装-构造函数模式

特点: 通过new创建对象,类型是确定的,并且对象和构造函数之间是存在联系的

js
function superCarFactory(name,color) {
    this.name = name
    this.color = color
}

var obj = new superCarFactory("红旗","black");
console.log(obj)
console.log( obj instanceof superCarFactory) // true

16.封装-原型模式

特点: 通过给构造函数的原型添加的属性能被实例继承

js
function superCarFactoryA(name,color) {
    this.name = name
    this.color = color
}

function superCarFactoryB(name,color) {
    this.name = name
    this.color = color
}

superCarFactoryA.prototype = superCarFactoryB.prototype
superCarFactoryB.a = 1

var car1 = new superCarFactoryA("红旗",black)
var car2 = new superCarFactoryB("法拉利",red)
console.log(car1.a,"car1") // 1
console.log(car2.a,"car2") // 1

17.原型和原型链

每个对象都有一个内置属性[[Prototype]]表示原型,但是并不是所有的对象都有原型,原型的主要作用是实现继承 通过下述方式创建的对象就没有原型

js
var obj = Object.create(null)

实现继承

js
const parent = {
    a: 1,
    fn() {
        console.log('父级的方法')
    }
}
const child = {}
child.__proto__ = parent
console.log(child.a)
child.fn()

构造函数的prototype

构造函数(箭头函数没有)都存在一个prototype属性,该属性与实例对象的[[Prototype]]相同 有2个需要注意的地方是:

  1. 构造函数的隐式原型是Function的prototype属性
  2. Function的隐式原型与Function的显示原型相同

18.原型链和原型链的继承

原型链决定了对象属性的访问方式,对象寻找属性会从本身开始查找,本身找不到会继续往原型上去找,直到找到原型链顶端null。

原型链的继承

原型链的继承主要是让字类的原型指向父类的实例,字类找不到自身对应的属性和方法的时候,就会往原型上查找,实现父类属性和方法的继承

缺点: 由于所有的Child实例都指向同一个Parent实例,对某一个Child实例的父类引用类型变量修改会影响到所有的Child实例 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能

js
// 父类
function Parent() {
    this.name = '写代码像蔡徐抻'
}
// 父类的原型方法
Parent.prototype.getName = function() {
    return this.name
}
// 子类
function Child() {}

// 让子类的原型对象指向父类实例, 这样一来在Child实例中找不到的属性和方法就会到原型对象(父类实例)上寻找
Child.prototype = new Parent()
Child.prototype.constructor = Child // 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要

// 然后Child实例就能访问到父类及其原型上的name属性和getName()方法
const child = new Child()
child.name          // '写代码像蔡徐抻'
child.getName()     // '写代码像蔡徐抻'

20.构造函数继承

构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参

缺点: 继承不到父类原型上的属性和方法

js
function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
    return this.name
}
function Child() {
    Parent.call(this, 'zhangsan')   // 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上
}

//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法

21. 组合式继承

缺点: 每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅

js
function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
    return this.name
}
function Child() {
    // 构造函数继承
    Parent.call(this, 'zhangsan') 
}
//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child

//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // ['zhangsan']

22. 寄生式组合继承

为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行

js
function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
    return this.name
}
function Child() {
    // 构造函数继承
    Parent.call(this, 'zhangsan') 
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Parent.prototype  //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child

//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // ['zhangsan']

但这种方式存在一个问题,由于子类原型和父类原型指向同一个对象,我们对子类原型的操作会影响到父类原型,例如给Child.prototype增加一个getName()方法,那么会导致Parent.prototype也增加或被覆盖一个getName()方法,为了解决这个问题,我们给Parent.prototype做一个浅拷贝

js
function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
    return this.name
}
function Child() {
    // 构造函数继承
    Parent.call(this, 'zhangsan') 
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype)  //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child

//测试
const child = new Child()
const parent = new Parent()
child.getName()                  // ['zhangsan']
parent.getName()                 // 报错, 找不到getName()

23. 防抖函数

当事件在n秒内连续被触发,只执行最后一次

js
function debounce(func,wait) {
    var timer = null 
    return function() {
        if(timer !== null){
            clearTimeout()
        }
        timer = setTimeout(func,wait)
    }
}

24.节流函数

当事件连续被触发时,每隔n秒执行一次

js
function throttle(func,wait) {
    var timer = null
    return function() {
        var context = this
        var args = arguments
        if(!timer) {
            timer = setTimeout(function(){
                func.apply(context,args)
            },wait)
        }
    }
}

ES6

1.let 和 const块级作用域

特点:

  1. 不存在变量提升

  2. 暂时性死区 在声明变量之前,该变量都是不可使用的,这在语法上,称为"暂时性死区"

  3. typeof 不会百分之百不会报错

js
typeof bb // 不会报错
var bb


typeof aa //报错
let aa
  1. 不能重复声明变量

块级作用域的作用

块级作用域可以随意的嵌套,内部可以访问外部的变量,但是外部的变量不能访问内部的变量

const 的特殊作用

const声明的变量不可改变,必须要有一个初始值

2. Symbol

概念以及引入原因

是什么?ES6 引入了的一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

作用?保证每个对象属性的名字都是独一无二的

Symbol 值不能与其他类型的值进行运算,会报错。
Symbol 值可以显式转为字符串。
Symbol 值也可以转为布尔值,但是不能转为数值。
Symbol.iterator属性

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。
对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器。
Object.getOwnPropertySymbols方法
Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

实际应用场景

js
// 常量枚举 js没有枚举类型的
    const CODE_ONE = 1;
    const CODE_TWO = 2;
    // 产生一个问题 1 2 不能重复  重复的话判断就会出错
    // symbol 哪怕你写重复了也没问题
   // 私有属性
   let private = Symbol('private')
   var obj = {
    _name:'张三',
    [private]:'私有的属性',
    say:function(){
        console.log(this[private])
    }
   }

   console.log(Object.keys(obj))

3.Set数据类型及应用场景

特性

  1. 成员没有重复的
  2. 属于引用数据类型
  3. 引用数据类型比较是地址
  4. 支持链式调用 set和数组的转化
js
const set = new Set([1,2,3,4,5])

const a = Array.from(set)
const b = [...set]

利用set的特性,快速实现数组的去重

js
const set = new Set([1,2,3,4,5,4])
const s = [...set]
console.log(s)

set中的方法:

js
const set = new Set([1,2,3,4,5])

set.add(6) //set添加数组,直接添加到尾部,不能改变索引
console.log(set)

console.log(set.size) // 获取set集合中的数量

console.log(set.has(1)) // 获取set集合中是否存在参数1

set.add({}).add({})

set集合的遍历

js
const set = new Set([1,2,3,4,5,4])
set.forEach((value,key) => {console.log(value + "----" + key)})

for (const element of set) {
    console.log(element)
}

set还能实现数组的交集,并集,差集的计算

js
// 并集
const arr = [1,2,3]
const arr2 = [2,3,4]
const set = new Set([...arr,...arr2])
console.log([...set])

// 交集
const newArr = arr2.filter(item => result.has(item))
console.log(newArr)
// 差集
const newArr2 = arr2.filter(item => !result.has(item))
console.log(newArr2)

4.Map数据类型及应用场景

创建map的两种方式

// 构造函数创建
const map = new Map()
map.set({},"content")

// 字面量的方式创建
const map1 = new Map([[o,"content"]])

map的数据类型

js
const map = new Map()
console.log(map instanceof Map) // true
console.log(Object.prototype.toString.call(map))  // [Object Map]

map的变量

js
const map = new Map([
    ["张三": function() {}],
    ["李四": function() {}],
    ["王五": function() {}],
])


map.forEach((item,index) => {console.log(index + ":" + item)})
// 张三:function() {}
// 李四:function() {}
// 王五:function() {}


for (const element of map) {
    console.log(element)
}

// ['张三', ƒ]
// ['李四', ƒ]
// ['王五', ƒ]

原生具备Iterator接口的数据结构如下

Array Map Set String

5.箭头函数

箭头函数和普通函数的区别

  1. this的指向问题 箭头函数本身是没有this的,他的this是从他作用域链的上一层继承来的,并且无法通过call和apply改变this指向
js
// 第一题
var fn = function () {
  return () => { console.log(this.name) }
}
var obj1 = {
  name: '张三'
}
var obj2 = {
  name: '李四'
}
var name = '王五'
obj1.fn = fn
obj2.fn = fn
obj1.fn()()
obj2.fn()()
fn()()

// 第二题
var user = {
  name: '张三',
  fn: function () {
    var obj = {
      name: '李四'
    }
    var f = () => this.name
    return f.call(obj)
  }
}
  1. 不能作为构造函数 没有prototype属性
  2. 没有arguments对象
  3. 不能使用yield命令,因此箭头函数不能用作 Generator 函数

6.函数新扩展的方法

给函数的参数指定默认值,看下面的两个题

js
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
console.log(m1())
console.log(m2())
console.log(m1({x: 3}))
console.log(m2({x: 3}))

通过rest参数获取函数的多余参数

js
function fn (x, ...y) {
  console.log(x)
  console.log(y)
}
fn(1, 2, 3, 4)

7.Promise

Promise 是异步编程的一种解决方案 Promise的三种状态

待定(pending): 初始状态,既没有被兑现,也没有被拒绝。 已兑现(fulfilled): 意味着操作成功完成。 已拒绝(rejected): 意味着操作失败。
Promise的状态一旦状态改变,就不会再变

promise相关的方法

Promise.resolve()
Promise.resolve()方法会返回一个状态为fulfilled的promise对象。

js
Promise.resolve(2).then((val) => {
  console.log(val)
})

Promise.reject()
Promise.reject()方法返回一个带有拒绝原因的Promise对象。

js
Promise.reject({ message: '接口返回错误' }).catch((err) => {
  console.log(err)
})

Promise.all()
Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型),返回一个promise实例。

js
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => { 
    resolve('hello')
  }, 1000);
});
const promise4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('world')
  }, 2000);
});

Promise.all([promise1, promise2, promise3, promise4]).then((values) => {
  console.log(values);
});

面试题

js
function fn () {
  return new Promise((resolve) => {
    console.log('Promise1')
    fn1()
    setTimeout(() => {
      console.log('Promise2')
      resolve()
      console.log('Promise3')
    }, 0);
  })
}
async function fn1() {
  var p = Promise.resolve().then(() => {
    console.log('Promise6')
  })
  await p.then(() => {
    console.log('Promise7')
  })
  console.log('end')
}
console.log('script')
setTimeout(() => {
  console.log('setTimeout')
}, 0)
fn().then(() => {
  console.log('Promise4')
})

8.Promise手写

想要进行手写Promise,首先需要知道promise 的基本特性及用法

  • promise 有三种状态,pending等待、fulfilled 完成、rejected 失败
  • pending 可以转为 fulfilled,也可转为 rejected,一旦状态转变了,状态不可再更改
  • resolve 为成功,状态由 pending 转为 fulfilled,并且接收参数 value
  • reject 为失败,状态由 pending 转为 rejected,并且接收参数 reason
  • then 方法接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected
  • 支持链式调用,可以在创建promise后通过.then一直触发
  • 静态方法的实现,比如Promise.resolve()和Promise.reject(),Promise.all()方法 实现一个最基本的 promise案例,这个是我们代码完成后要实现的效果
js
    let p = new Promise((resolve, reject) => {
      resolve('success')
      // reject('error')
    })
    p.then(data => {
      console.log(data)
    })

1.1 需要创建一个类,并且添加一个回调方法 executor,作为Promise 的参数

js
    class MPromise {
     constructor(executor) {
        // 回调函数
        executor()
      }
    }

1.2 executor接收resolve 和 reject 作为参数

js
    class MPromise {
     constructor(executor) {
         //成功的参数
        this.value = undefined
         //失败的参数
        this.reason = undefined
         
        const resolve = (value) => {
          this.value = value
        }
        const reject = (reason) => {
          this.reason = reason
        }
        executor(resolve, reject)
      }
    }

1.3 定义promise的三种状态,实现then方法

js
    class MPromise {
     constructor(executor) {
        //成功的参数
        this.value = undefined
        //失败的参数
        this.reason = undefined
        // 定义promise的状态
        this.state = 'pending'
        
        const resolve = (value) => {
          if (this.state === 'pending') {
            // 状态修改为成功
            this.state = 'fulfilled'
            this.value = value
          }
          
        }
        const reject = (reason) => {
          // 状态修改为失败
          if (this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
          }
        }
        executor(resolve, reject)
      }
      // 实现 then 方法
      then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
          // 接收成功传来的数据
          onFulfilled(this.value)
        }
        if (this.state === 'rejected') {
          // 接收失败传来的数据
          onRejected(this.reason)
        }
      }
    }

执行案列

js
    const p = new MPromise((resolve, reject) => {
      resolve('success')
    })
    console.log(p)
    p.then((val) => {
      console.log(val, 'val')
    })

很显然 上面的案例是一个同步操作,我们来看下第二个案例

js
    const p = new MPromise((resolve, reject) => {
      // 添加定时器
      setTimeout(() => {
          resolve('success')
      }, 1000)
    })
    console.log(p, 'p')
    p.then((val) => {
      // 无法触发
      console.log(val, 'val')
    })

原因:

因为到目前为止,实现的 promise 都是同步的,当我们执行 executor 时先把同步操作执行完成,发现有一个异步操作 settimeout,先让他去排队了(这里需要了解一下事件循环机制),然后立刻去同步执行了 then 方法。

解决思路:

既然 then 自己无法知道 resolve 什么时候执行,是否执行了,那resolve执行完后就需要有个东西告诉then,执行完了。

js
    class MPromise {
     constructor(executor) {
        //成功的参数
        this.value = undefined
        //失败的参数
        this.reason = undefined
        // 定义promise的状态
        this.state = 'pending'
        // 定义数组,存放稍后要完成的任务
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
    
        const resolve = (value) => {
          if (this.state === 'pending') {
            // 状态修改为成功
            this.state = 'fulfilled'
            this.value = value
            /* 成功了,在这个例子中,相当于过了 1秒了开始执行resolve了,
            状态改变后,把我们预约好的任务拿出来依次执行 */
            this.onResolvedCallbacks.forEach(fn => fn())
          }
          
        }
        const reject = (reason) => {
          // 状态修改为失败
          if (this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        executor(resolve, reject)
      }
      // 实现 then 方法
      then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
          // 接收成功传来的数据
          onFulfilled(this.value)
        }
        if (this.state === 'rejected') {
          // 接收失败传来的数据
          onRejected(this.reason)
        }
        if(this.state === 'pending') {
          /* 因为异步导致 state 还在 pending 状态 
          所以把 要做的任务先放到预约的数组队列里
          */
          this.onResolvedCallbacks.push(() => {
            onFulfilled(this.value)
          })
          this.onRejectedCallbacks.push(() => {
            onRejected(this.reason)
          })
        }
      }
    }

2.1 链式调用

链式调用的本质是需要调用then方法后返回一个promise对象

js
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('延迟1秒')
      }, 1000)
    })
    p.then(data => {
      let str = data + '我再第一个 then 里!'
      console.log(str)
      return str
    }).then(data => {
      let str = data + '我在第二个 then 里!'
      console.log(str)
    })

代码实现

js
    class MPromise {
     constructor(executor) {
        //成功的参数
        this.value = undefined
        //失败的参数
        this.reason = undefined
        // 定义promise的状态
        this.state = 'pending'
        // 定义数组,存放稍后要完成的任务
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
    
        const resolve = (value) => {
          if (this.state === 'pending') {
            // 状态修改为成功
            this.state = 'fulfilled'
            this.value = value
            /* 成功了,在这个例子中,相当于过了 1秒了开始执行resolve了,
            状态改变后,把我们预约好的任务拿出来依次执行 */
            this.onResolvedCallbacks.forEach(fn => fn())
          }
          
        }
        const reject = (reason) => {
          // 状态修改为失败
          if (this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        executor(resolve, reject)
      }
      // 实现 then 方法
      then(onFulfilled, onRejected) {
        return new MPromise((resolve, reject) => {
          if (this.state === 'fulfilled') {
            // 接收成功传来的数据
            resolve(onFulfilled(this.value))
          }
          if (this.state === 'rejected') {
            // 接收失败传来的数据
            reject(onRejected(this.reason))
          }
          if(this.state === 'pending') {
            /* 因为异步导致 state 还在 pending 状态 
            所以把 要做的任务先放到预约的数组队列里
            */
            this.onResolvedCallbacks.push(() => {
              resolve(onFulfilled(this.value))
            })
            this.onRejectedCallbacks.push(() => {
              reject(onRejected(this.reason))
            })
          }
        })
      }
    }

2.2 添加静态方法

js
    class MPromise {
     constructor(executor) {
        //成功的参数
        this.value = undefined
        //失败的参数
        this.reason = undefined
        // 定义promise的状态
        this.state = 'pending'
        // 定义数组,存放稍后要完成的任务
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
    
        const resolve = (value) => {
          if (this.state === 'pending') {
            // 状态修改为成功
            this.state = 'fulfilled'
            this.value = value
            /* 成功了,在这个例子中,相当于过了 1秒了开始执行resolve了,
            状态改变后,把我们预约好的任务拿出来依次执行 */
            this.onResolvedCallbacks.forEach(fn => fn())
          }
          
        }
        const reject = (reason) => {
          // 状态修改为失败
          if (this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        executor(resolve, reject)
      }
      // 实现 then 方法
      then(onFulfilled, onRejected) {
        return new MPromise((resolve, reject) => {
          if (this.state === 'fulfilled') {
            // 接收成功传来的数据
            resolve(onFulfilled(this.value))
          }
          if (this.state === 'rejected') {
              // 接收失败传来的数据
              try {
                  if (!onRejected) {
                      reject(onFulfilled(this.reason))
                  } else {
                      reject(onRejected(this.reason))
                  }
              } catch (err) {
                  console.log(err)
              }
          }
          if(this.state === 'pending') {
            /* 因为异步导致 state 还在 pending 状态 
            所以把 要做的任务先放到预约的数组队列里
            */
            this.onResolvedCallbacks.push(() => {
              resolve(onFulfilled(this.value))
            })
            this.onRejectedCallbacks.push(() => {
              reject(onRejected(this.reason))
            })
          }
        })
      }
      static resolve(value) {
        return new MPromise(resolve => resolve(value))
      }
      static reject(reason) {
        return new MPromise((resolve, reject) => reject(reason))
      }
      static all(promises) {
        return new MPromise((resolve, reject) => {
          // 存储promise的结果
          const results = []
          let count = 0
          promises.forEach((promise, index) => {
            // 获取到每个promise然后执行
            promise.then(value => {
              results[index] = value
              count++
              if (count === promises.length) {
                resolve(results)
              }
            })
          })
        })
      }
    }

9.async和await

async的出现让我们可以用一种更简洁的方式写出基于Promise的异步行为

js
function p () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('异步结果')
    }, 1000)
  })
}

function fn() {
    p().then(val => {
        console.log(val)
    })
}


async function fn1() {
    const res = await p()
    console.log(res)
}

async函数的返回值为一个promise,通过then和catch来捕获内部的返回值

特性;

  1. async函数内部会返回一个promise对象,如果看起来不是promise,那么它将会隐式的包装在promise中
  2. await能获取到promise状态改变后的值,如果后面不是一个promise,await 会把该值转换为已正常处理的Promise
  3. await后面promise的状态是reject,则await后的代码不会执行,async函数将返回状态为reject的promise
  4. async函数内部如果存在await,await表达式会暂停整个async函数的执行,等当前位置promise状态改变后才能恢复
js
async function fn () {
  setTimeout(function () {
    console.log(1)
  }, 0)
  Promise.resolve().then(() => console.log(4))
  await setTimeout(function () {
    console.log(5)
  }, 0)
  await Promise.resolve().then(() => console.log(6))
  Promise.resolve().then(() => console.log(7))
  console.log(3)
}
fn()

10.Proxy

基本语法

Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。属于元编程,即对编程语言进行编程

创建Proxy的两种方式

js
const target = {}
const handler = {
    // 当读取属性时,调用
    get(target,property) {}
    // 当修改属性时,调用
    set(target,property,value) {}
}
const p = new Proxy(target,handler) 


// 创建可撤销的代理对象,当调用revoke()时,当前的proxy已经取消了代理

const { Proxy,revoke } = p = Proxy.revocable()

拦截陷阱

js
const obj = {
    name: "孙悟空"
}
const obj2 = {
    name: "猪八戒",
    get value () {
        return this.name
    }
}

const { proxy:p,revoke } = Proxy.revocable(obj2,{
    get(target,property,receiver) {
        return target[property]
    }
})

解决办法

js
const obj = {
    name: "孙悟空"
}
const obj2 = {
    name: "猪八戒",
    get value () {
        return this.name
    }
}

const { proxy:p,revoke } = Proxy.revocable(obj2,{
    get(target,property,receiver) {
        return Reflect.get(target,property,receiver)
    }
})

// 将obj2的代理对象作为obj的原型
Object.setPrototypeOf(obj,p)
console.log(obj.value) // 孙悟空

11.Class

class可以理解为是一个语法糖,将js只能通过构造函数创建实例的方法进行了补充。

class的特性

  1. class的数据类型是一个函数
js
console.log(typeof class A {})  // "function"
  1. class的原型的constructor指向class(与构造函数相同)
js
class A {}

console.log(A.prototype.constructor === A)  //true
  1. 通过 new 关键字创建出的实例的constructor指向该class
js
class A {}
const a = new A()
console.log(a.contrucor === A) // true
  1. class内部的方法实际上都是定义在类的prototype上
  2. 通过类创建对象的本质是调用类的constructor,如果类未定义constructor,则会默认添加一个constructor
  3. class不能直接调用,需要通过new 关键字
js
class A {}
A() // 报错
  1. class内部的方法指向的是实例,class内部是严格模式

claa的其他语法

取值函数(getter)和存值函数(setter)

js
get name() {
    return "1"
}
set name(value){
    console.log("setter:" + value)
}

类的属性名可以动态设置

js
var methodName = "methodName"
class A {
    [methodName]() {}
}

new A()

静态方法/属性
静态的方法和属性是不会继承到实例身上的,并且静态方法中的this指向的是类 class,而不是实例

12.class继承

class的继承是通过extends关键字来实现的

js
class F{
    money = "100w"
    fn() {}
}

class S extends f{}

console.log(new S())

字类通过调用super()调用父类的constructor并将参数传递过去,在super调用前子类是没有this,如果使用会报错

子类在继承的过程中,属性会直接添加到实例上,但是方法会保留在父类的原型链中

class继承的本质还是利用原型链实现的

js
class F {
    money = "100w"
    fn() {}
}

class S extends F {
    constructor() {
        super()
        this.name = "son"
    }
    fn1() {}
}

var s = new S()
console.log(s.prototype.__proto__ === F.prototype) // true