2023每天只卷一点点

221 阅读3分钟

HTML和CSS篇

1.BOM和DOM

BOM

浏览器对象模型,使JavaScript 有能力与浏览器"对话"。BOM提供了很多对象,用于访问浏览器的功能,这些功能与任何网页内容无关。

DOM

文档对象模型,用于访问和操作HTML文档和XML文档

JS篇

1.script元素

浏览器在解析HTML的时候,如果遇到一个没有任何属性的script标签,就会暂停解析,先发送网络请求获取JS脚本的内容,然后让js引擎执行该代码,当代码执行完毕后恢复解析。如果获取JS脚本的网络请求迟迟得不到响应,或者js脚本的执行时间过长,就会导致白屏。

defer属性

defer属性表明脚本在执行时不会影响页面的构造,也就是说,脚本会被延迟到整个页面都解析完毕后再运行。相当于告诉浏览器立即下载,但延迟执行。

async属性

async属性只适用于外部脚本文件,并告诉浏览器立即下载文件,但与defer不同的是,标记为async的脚本并不保证按照指定它们的先后顺序执行。指定async属性的目的是不让页面等待脚本文件的下载与执行,从而异步加载页面的其他内容。

2.原始类型与引用类型

在Javascript引擎中右两个地方可以存储数据,分别是栈和堆,原始类型做赋值操作的时候,是直接将变量和值存储到栈中,而引用类型赋值操作确是在堆中存储值,在栈中存储变量并指向对应堆中值的地址。

3.对象

在JavaScript中,几乎所有的对象都是Object类型的实例,他们都会从Object.prototype继承属性和方法。

学习对象前我们首先要知道什么是描述对象: 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。通过Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

分别为:

value: 该属性的值

writable: 该属性的值是否可读

get: 获取该属性的访问器函数(getter)

set: 获取该属性的设置器函数(setter)

configurable: 该对象的属性描述符是否可以被更改或属性是否可以被删除

enumerable: 对象的属性是否可以被枚举

Object.defineProperty()

了解了描述对象后,我们就可以通过Object.defineProperty()在对象上定义我们需要的新属性,或者是修改一个对象的现有属性。

语法:

Object.defineProperty(obj, prop, descriptor)

注意: Object.defineProperty只对初始对象里的属性有监听作用,而对新增的属性无效。这也是为什么Vue2中对象新增属性的修改需要使用Vue.$set来设值的原因。

4.Proxy

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

语法:

const p = new Proxy(target, handler)

target: 要使用Proxy包装的目标对象

handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。

Proxy实例的方法可以参考阮一峰的《ES6入门教程》或者MDN网站。

5.Class的基本语法

  1. class中的this代表实例对象
  2. constructor就是ES5中 的构造函数
  3. 类的数据类型就是函数,类本身就指向构造函数
  4. function和类中,类=== 类.prototype.constructor === 实例.__ proto__.constructor
  5. 构造函数的prototype属性在ES6的class上继续存在
  6. 类的所有方法都定义在类的prototype属性上面
  7. 在类的实例上调用方法,其实就是调用原型上的方法
  8. 类的方法都定义在prototype对象上面,所以新的方法可以添加在prototype对象上面
  9. constructor方法是类的默认方法,通过new 命令生成对象实例时,自动调用该方法
  10. __ proto__属性会改写原型,不建议使用(ES2022中规定了新写法)
  11. 静态方法:在方法前面加上static,表示该方法不会被实例继承,而是通过类直接调用(可以被子类继承,也可以从super对象上调用)
  12. 如果静态方法中包含this关键字,则这个this指的是类。而不是实例
  13. 静态属性的写法和静态方法不一样,静态方法只能通过Point.name这种形式声明(因为ES6里面规定类里面只有静态方法,没有静态属性)
  14. ES2022正式为class添加私有属性(只能在类的内部使用,实例可以引用,私有属性可以被继承)方法是在属性名之前使用#表示
  15. in操作符可以判断是否存在静态属性
  16. ES2022引入静态块,允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要是对静态属性进行初始化
  17. 每个类中可以有多个静态块,每个静态块只能访问之前声明的静态属性,静态块内部不能有return语句

类的定义有两种方式

// 类声明
class Person {}
// 类表达式
const Animal = class {};

与函数表达式类似,类表达式在他们被求值之前也不能引用,不过,与函数定义不同的是,虽然函数声明可以提升,但类定义不能。

另一个与函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制

类的构造函数

1.实例化

constructor关键字用于在类定义块内部创建类的构造函数。在使用new操作符创建类的新实例时,会自动调用constructor这个构造函数

使用new调用类的构造函数会执行如下操作

(1)在内存中创建一个新对象

(2)这个新对象的Prototype指针被赋值为构造函数的prototype属性

(3) 构造函数内部的this被赋值为这个新对象(即this指向新对象)

(4)执行构造函数内部的代码(给新对象添加属性)

(5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

2.把类当作特殊函数

通过typeof操作符检测类标识符,表明他是一个函数

class Person {}
console.log(Person); // class Person {}
console.log(typeof Person) // function

类标识符有prototype属性,而这个原型也有一个constructor属性指向类自身:

class Person {}
console.log(Person.prototype);  // {constructor: ƒ}
console.log(Person === Person.prototype.constructor); // true

与普通构造函数一样,可以使用instanceof操作符检查构造函数原型是否存在于实例的原型链中:

class Person {}
let p = new Person();
console.log(p instanceof Person); // true

实例、原型和类成员

1.原型方法与访问器

为了在实例间共享方法,类定义语法把类块中定义的方法作为原型方法。

class Person {
    constructor() {
      // 添加到this的所有内容都会存在于不同的实例上
      this.locate = () => console.log('instance');
    }
    // 在类块中定义的所有内容都会定义在类的原型上
    locate() {
      console.log('prototype');
    }
}
let p = new Person();
p.locate(); // instance
Person.prototype.locate(); // prototype

可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或者对象作为成员数据:

class Person {
    name: 'Jake'
}
// Uncaught SyntaxError: Unexpected token

类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键

类定义也支持获取和设置访问器。语法与行为跟普通对象一样:

class Person {
    set name(newName) {
      this.name = newName;
    }
    get name() {
      return this.name_;
    }
}
let p = new Person();
p.name = 'Jack';
console.log(p.name) // Jake

可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似,静态成员每个类上只能有一个。

静态成员在类中使用static关键字作为前缀。在静态成员中,this引用类自身。其他所有约定跟原型成员一样:

class Person {
    constructor() {
        this.locate = () => console.log('instance', this);
    }
    locate() {
        console.log('prototype', this)
    }
    static locate() {
        console.log('class', this);
    }
}
let p = new Person();
p.locate(); // instance Person {}
Person.prototype.locate(); // prototype, {constructor: ...}
Person.locate(); // class, class Person {}

静态类方法非常适合作为实例工厂:

class Person {
    constructor(age) {
        this.age_ = age;
    }
    sayAge() {
        console.log(this.age_);
    }
    static create() {
        return new Person(Math.floor(Math.random() * 100));
    }
}
console.log(Person.create()); // Person { age: ... }

虽然类定义并不显示支持在原型或类上添加成员数据,但在类定义外部,可以手动添加:

class Person {
    sayName() {
        console.log(`${Person.greeting} ${this.name}`);
    }
}
// 在类上定义数据成员
Person.greeting = 'Mynameis';
Person.prototype.name = 'Jake';
let p = new Person();
p.sayName(); My name is Jake

注意:类定义中之所以没有显式支持添加数据成员,是因为在共享目标(原型和类)上添加可变(可修改)数据成员是一种反模式。一般来说,对象实例应该独自拥有通过this引用的数据。

性能优化篇

1.前端SEO

什么是SEO

搜索引擎的数据库中存储着海量的关键字,每个关键字后面都对应着很多网址,这些网址是搜索引擎的爬虫程序在互联网上一点点收集来的,搜集来的大量网址,会按照关键字的匹配程度或算法进行排序,从而让用户拥有更好的使用体验。这个将网址进行排序的算法,就是SEO。

如何根据SEO进行优化呢

  1. 网站结构尽可能简单,网站结构层级越少,越容易被爬虫抓取,因为如果访客跳转3次还没找到需要的信息,很可能会离开,尽量让爬虫跳转3次就能到达网站的任何一个网页。
  2. 减少接口请求的数量以及页面资源大小,但速度很慢的时候,爬虫程序容易离开
  3. 关键词不要重复出现,每个页面的title中不要设置相同的内容。
  4. 多使用语义化标签,h1标签自带权重

为什么单页面应用不利于SEO

seo的本质是向服务端发起请求,解析请求的内容,但爬虫是不会解析请求中的js的,也就是说,爬虫抓起请求然后获取请求中的html代码进行解析,而单页面应用的html在服务端还没有渲染数据,在浏览器端才会渲染出数据,所以爬虫获取到的只是html模板,并不能获取页面的内容。

2.防抖和节流

防抖

防抖是指一段程序在只能时间内只执行一次,当这段程序程序还未执行时再次触发后,会重新计时。

手写防抖函数

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>防抖</title>
    </head>
    <body>
        <button id="debounce">点我防抖!</button>
​
        <script>
            window.onload = function () {
                var myDebounce = document.getElementById("debounce");
                myDebounce.addEventListener("click", debounce(sayDebounce));
            };
​
            function debounce(fn) {
                let timeout = null;
                return function () {
                    // 每次当用户点击的时候,初始化定时器,创建一个新的定时器
                    clearTimeout(timeout);
                    timeout = setTimeout(() => {
                        fn.call(this, arguments);
                    }, 1000);
                };
            }
​
            function sayDebounce() {
                console.log("防抖成功!");
            }
        </script>
    </body>
</html>

节流

节流是指程序在指定时间间隔内只会执行一次

手写节流函数

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>节流</title>
    </head>
    <body>
        <button id="throttle">点我节流!</button>

        <script>
            window.onload = function () {
                var myThrottle = document.getElementById("throttle");
                myThrottle.addEventListener("click", throttle(sayThrottle));
            };

            function throttle(fn) {
                // 通过闭包保存一个标记
                let canRun = true;
                return function () {
                    console.log("canRun", canRun);
                    // 如果是false说明已经触发过了
                    if (!canRun) {
                        return;
                    }
                    // 将 canRun 设置为 false,防止执行之前再被执行
                    canRun = false;
                    setTimeout(() => {
                        fn.call(this, arguments);
                        // 执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
                        canRun = true;
                    }, 1000);
                };
            }

            // 3、需要节流的事件
            function sayThrottle() {
                console.log("节流成功!");
            }
        </script>
    </body>
</html>

设计模式篇

1.开放封闭原则

开放封闭原则:对扩展开放,对修改封闭,增加需求时,扩展新代码。而非修改旧代码

举个例子:

let checkType=function(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$/.test(str)
        case 'mobile':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        default:
            return true;
    }
}
  • 如果想添加其他规则就得在函数里面增加 case 。添加一个规则就修改一次!这样违反了开放-封闭原则(对扩展开放,对修改关闭)。而且这样也会导致整个 API 变得臃肿,难维护。
  • 比如A页面需要添加一个金额的校验,B页面需要一个日期的校验,但是金额的校验只在A页面需要,日期的校验只在B页面需要。如果一直添加 case 。就是导致A页面把只在B页面需要的校验规则也添加进去,造成不必要的开销。B页面也同理。

建议的方式是给API增加一个扩展的接口:

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        }
    };
    //暴露接口
    return {
        //校验
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        //添加规则
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})();