学习笔记

学习笔记

一些js的基础知识

中台

概念

传统前台-后台架构,各个项目相对独立,各自造各自的轮子,会使项目越来越臃肿,开发效率越来越低。出现大量重复开发,用户体验不统一,无法支持大规模的用户增长等问题。

为了提高开发效率,就提出将需求高度相似、通用程度高的业务由专门的团队负责规划和开发。也就是一个专门造轮子的部门。

举例

数据中台:统一对数据进行采集、数据集成、数据治理,统一标准和口径,让数据产生更多的价值。它的本质就是“数据仓库+数据服务中间件”,作为各个业务的数据源,为业务系统提供数据和计算服务。 业务中台:各个项目的共同业务整合成统一的服务平台,例如支付、用户等等 技术中台:为各个项目的开发人员提供通用的底层框架、引擎等 算法中台:为各个项目提供算法能力,搜索、推荐、图像识别等等

Iaas基础设施服务\Paas平台服务\Saas软件服务

Iaas: 就是提供服务器、存储、网络等硬件支持服务 Paas: 软件部署平台,开发组只需要关心业务逻辑,不用关心底层实现 Saas: 软件服务,就是拿来即用的,不需要关心技术

JavaScript相关

执行上下文和执行栈

执行上下文:分全局和函数执行上下文,函数被调用时创建一个执行上下文,按被定义的顺序执行;不在函数中的代码均在全局上下文中执行,会创建一个window对象并让this指向这个对象。 执行栈: LIFO(后进先出),执行一段程序,先创建全局上下文,遇到一个函数调用就创建一个函数上下文压入执行栈顶部,引擎执行完一个函数就会将它弹出执行栈。

执行上下文分为创建阶段执行阶段

创建阶段分三个步骤:

  • this的绑定
  • 创建词法环境
  • 创建变量环境

this的值

this的值取决于函数是如何被调用的,如果是被一个引用它的对象调用,那么this的值就是这个对象,否则就是全局对象或者undefiend(严格模式)。eg: obj.fn()的fn中的this就是obj,但是bar=obj.fn;bar()中的this就是全局了。

  • 由new调用:绑定到新创建的对象
  • 由call或apply、bind调用:绑定到指定的对象,[].slice(argument)相当于arugment.slice()
  • 由上下文对象调用:绑定到上下文对象
  • 默认:全局对象 注意:箭头函数不使用上面的绑定规则,根据外层作用域来决定this,继承外层函数调用的this绑定。

词法环境

定义标识符(变量名称)和变量/函数之间的关联,一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。全局环境中外部环境引用为null,有内建的Object/Array等函数;函数环境中用户定义的变量存储在环境记录器中,外部环境引用为全局或者包含此函数的外部函数。每个函数对父级作用域的引用构成了作用域链。

变量环境

也是一个词法环境,和词法环境的区别就是用来记录var定义的变量,且初始值设为undefined。

函数的作用域取决于函数创建的位置,而不是执行的位置,就是静态作用域

变量提升

编辑节点,代码执行前,所有变量和函数的声明会被添加到词法环境中,所有可以在声明的代码前访问到变量和函数。但是,声明和赋值是两步操作,声明会被提升,赋值不会

1
2
3
4
5
6
7
var myname = 'chenz';
function getName() {
// 函数内部没有声明就会向上查找到window.myname,后面语句中的变量声明会被提升,所以访问的是内部的myname
console.log(myname);
if(0) { var mayname = 'xxx' } // 条件中的语句虽然不会被执行,但是声明会被识别到,只是没有被赋值
console.log(myname);
}

原型与原型链

一个以隐式引用作为存储方式,以点操作符和属性访问语句作为语法糖的单向链表。

构造函数、实例对象、原型对象

js中万物皆对象,分为普通对象和函数对象

  • 普通对象添加prototype属性就是原型对象
  • 函数对象创建时就会自带prototype属性 构造函数就是设计图,实例对象就是生产的产品 实例对象的proto就是构造函数的prototype,也就是原型对象 实例对象的constructor就是proto的constructor,也就是原型对象的constructor

原型prototype,就是给其它对象提供共享属性的对象,prototype 描述的是两个对象之间的某种关系(其中一个,为另一个提供属性访问权限)。 对象的__proto__属性实际上指向它的prototype对象的proto的get()方法。 每个对象都有一个__proto__属性,指向构造函数的prototype对象 每个对象都有自己的__proto__,构成了对象的原型的原型的原型的链条,直到某个对象的隐式引用为 null,整个链条终止。

原型的继承

Object.setPropertyOf 和 Object.create都可以实现原型的显式继承(即手动继承),区别在于:

  • Object.setPrototypeOf,给我两个对象,我把其中一个设置为另一个的原型。
  • Object.create,给我一个对象,它将作为我创建的新对象的原型。 JavaScript 提供了隐式的原型继承方式,实现了下面这几个步骤:
  1. 创建空对象
  2. 设置该空对象的原型为另一个对象或者 null
  3. 填充该对象,增加属性或方法。

通过 new 去创建 user 对象,可以通过 user.consturctor 访问到它的构造函数。

1
2
3
4
5
6
7
// constructor函数
function User(firstName, lastName) {
this.firstName = fristName;
this.lastName = lastName;
}
const user = new User('zeng', 'chen');
console.log(user.constructor === 'User'); // true

实际javasscript会隐式的完成完成创建对象、原型继承和属性初始化的过程

1
2
3
4
const user = {};
Object.setPrototypeOf(user, User.prototype);
user.firstName = 'chenz';
user.lastName = 'zeng';

使用对象字面量创建对象

1
2
3
4
const user = {
firstName: 'chen',
lastName: 'zeng',
}

等价于

1
2
3
const user = new Object();
user.firstName = 'chen';
user.lastName = 'zeng';

继承

原型链继承

子类构造函数的原型指向父类构造函数 缺点:

  • 父类的属性被所有子类共享
  • 创建子类时无法向父类传参

借用构造函数(经典继承)

子类构造函数的this通过call指向父类构造函数,解决了原型链继承的问题 缺点:

  • 只能继承父类属性/方法,不能继承父类原型属性/方法
  • 方法都在构造器中定义,每次创建实例都要创建一遍方法

组合继承

用原型链继承实现对原型属性/方法的继承,用借用构造函数来实现对实例属性/方法的继承 缺点:

  • 两次调用父类构造函数

设计模式

工厂模式

简单工厂模式

又叫静态工厂模式,用于创建同一类产品的实例,根据传入的参数不同,创建的实例的属性就不同。 常用场景:用户权限(根据传入角色返回不同的实例)、vue-router(不同角色配置不同的路由)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Factory(name,age){
let person = {};
person.name = name;
person.age = age;
person.say = function(){
return this.name;
};
return person;
}
let tom = new Factory('Tom','10');
let jerry = new Factory('Jerry','20');
// ES6
class Factory {
//构造器
constructor(opt) {
this.name = opt.name;
this.age = opt.age;
}
say() {
return this.name;
}
}
工厂方法模式

抽象一个工厂用于创建一个工厂,将其成员对象的实例化推迟到子类中,子类可以重写父类接口方法以便创建时指定独自的对象类型,父类变为抽象类,不能被实例只能被继承。这样的好处是通用方法写在工厂函数中,不需要重复实现,不同个性化代码在子类中实现。 就是将简单工厂模式中的通用部分提取到一个父类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User {
constructor(opt = {}) {
if(new.target === User) {
throw new Error('抽象类不能实例化!');
}
this.name = opt.name;
this.age = opt.age;
}
say() {
return this.name;
}
}
class Factory extends User {
constructor(opt) {
super(opt);
}
create(opt) {
return new Factory(opt);
}
}

单例模式

针对全局仅需一个对象的场景,如线程池、全局缓存、window 对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let CreateSingleton = (function(){
let instance;
return function(name) {
if (instance) {
return instance;
}
this.name = name;
return instance = this;
}
})();
CreateSingleton.prototype.getName = function() {
console.log(this.name);
}

let Winner = new CreateSingleton('Winner');
let Looser = new CreateSingleton('Looser');

console.log(Winner === Looser); // true
console.log(Winner.getName()); // 'Winner'
console.log(Looser.getName()); // 'Winner'

观察者模式

定义了一种对象之间的一对多的关系,只要当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题。常用的DOM事件绑定就是一种观察者模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 主体对象
class SubjectObj {
constructor() {
this.observers = []; // 观察者列表
this.internalState = 10;
}
// 更新状态,触发观察者对象更新
notify(value) {
this.internalState = value;
this.observers.forEach(observerFn => observerFn(value));
}
// 注册观察者
registerObserver(observerFn) {
this.observers.push(observerFn);
}
}

发布-订阅模式

类似于观察者模式,但是多了一个中间层,增加了消息队列,改为异步推送。

模块化

全局方法 –> 对象封装 –> 自调用函数(闭包) –> script引入 好处:

  • 避免命名冲突
  • 按需加载
  • 更好维护
  • 更好复用 问题:
  • 请求过多
  • 依赖关系混乱 为了解决这些问题,产生了模块化规范commonJS、AMD、CMD

commonJS

Node采用的规范,一个文件就是一个模块,其中的变量、方法都是私有的。服务器端是同步加载的,客户端需要编译。 暴露模块:module.exports = value或者exports.xxx = value,module在模块内部代表模块本身 引入模块:require(),调用是读入并执行一段Javascript并查找exports属性,没找到会报错 输入的值为输出值的拷贝,如果是一个原始类型的值,模块内部值的变动就不会影响已经引入的值 加载模块为同步加载,只有加载完成了才能进行下一步操作,比较适合服务器端,因为文件都是本地加载

AMD

异步模块加载,RequireJS定义的规范,使用define方法进行模块的定义

1
2
3
// 数组引入依赖,回调参数为引入的依赖
// 依赖前置,一开始就引入了依赖
define([module1, module2], function(module1, module2) {})

CMD

延迟模块加载,与AMD的主要区别:

  • AMD为提前执行,CMD为延迟执行
  • AMD为依赖前置,CMD为依赖就近
1
2
3
4
5
6
7
8
9
define(function(require, exports, module){
// requre用于引入依赖
//引入依赖模块(同步)
var module2 = require('./module2')
//引入依赖模块(异步)
require.async('./module3', function (m3) {})
// exports、module用于暴露模块
exports.xxx = value;
})

跨域

浏览器同源策略,“协议+域名+端口”必须相同,才能访问资源,三者中任意一个不同的情况访问资源都算作“跨域” 前端只能实现域名不同的跨域。 请求实际已经发出去了,只是返回时浏览器如果判断是跨域请求,就拦截了,所以无法完全阻止CSRF(跨站请求伪造) 不受跨域限制:

  • img的src
  • link的href
  • script标签的src

jsonp

利用script标签src不受跨域限制实现的跨域方案,动态获取json数据。 缺点:类似于同源的ajax,但是只支持get方法,有局限性,且容易遭受xss攻击(跨站脚本攻击) PS:xxs攻击,就是在本应是文本的地方输入/传入一段脚本,从而达到攻击的目的,

  • 存储型,持久,只要访问存储有恶意代码的数据,就会被攻击
  • 反射型,非持久,只有访问特定的URL才会被攻击 危害:
  • 劫持访问,跳转到其他页面
  • 盗取cookie,实现无密码登录,可使用验证码、http-only cookie进行防范(禁止Javascript访问cookie)
  • 配合CSRF,实现修改密码、银行转账等,可通过原密码校验、短信验证码等进行防范 防范:
  • 过滤用户输入,对能实现xss的script、img、a等标签进行过滤
  • 字符转义,让浏览器正确显示字符的同时不作为代码执行
  • 限制用户输入,对于可预期的输入进行限制,例如邮箱、电话号码等

深浅拷贝

深浅拷贝主要针对引用型数据,浅拷贝只拷贝引用不拷贝值

  • = 赋值操作符是浅拷贝
  • JSON.stringify()和JSON.parse()转换可以实现深拷贝,但是不能拷贝属性是function/undefined/symbol
  • 数组/对象自带的拷贝方法均是首层浅拷贝,只有第一层是深拷贝,二维就是浅拷贝了
  • 只能递归才能实现真正的深拷贝

事件机制

  • Event Table 用于注册异步回调ß
  • Event Queue 事件队列,主线程空闲时就会执行队列中的任务
  • 主线程是唯一的,但是事件队列可以有多个,分宏任务和微任务,process.nextTick和Promise属于微任务,setTimeout本身属于宏任务在第一轮循环执行注册回调,然后微任务,然后第二轮执行宏任务执行回调。

柯里化

柯里化,就是将一个多参数的函数,转换成一系列使用一个参数的函数 作用:参数复用,对于多次调用同一个函数的情景,使用柯里化转换后的函数,相同的参数就不用重复传了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将普通函数柯里化
function curry(func) {
return function curried(...args) {
// func.length表示func这个普通函数的参数个数
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
// curriedFn(a)(b,c)的场景,concat后再次调用参数就是(a,b,c)了,走第一个if
return curried.apply(this, args.concat(args2));
}
}
};
}

promise

回调函数:a函数作为参数传给b函数,需要依赖b函数的调用,容易嵌套层数过多引起混乱 JavaScript异步解决方案 一个Promise对象有三种状态,只能从pendding到另外一种状态,且变更后凝固不再变化

  • Pending 进行中
  • Fulfilled 已完成
  • Rejected 已拒绝 Promise对象 的then()方法接收一个函数参数,函数的两个参数分别是onFulfilled和onRejected,返回一个新的Promise(因为原来的Promise状态已经凝固) catch()方法是then(null/undefined, onRejected)的别名,捕获reject()和then的回调中的错误,错误会“冒泡”,最后的catch会捕获前面所有的错误 构造函数Promise()接收两个方法参数,resolve()方法将Pending变更为Fulfilled,reject()方法将Pendding变更为Rejected 构造函数中的代码将会立即执行,then()方法中的代码总是异步执行 立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务 Promise.allSettled(),接收数组,所有promise都finally才返回结果,只会变为Fulfilled,回调参数为数组

函数式编程

就是声明各种纯函数,拼接成管道来完成任务

  • 数据不可变,不会修改已有的数据(外部变量),新增进行处理
  • 无状态,函数输出只取决于传入的参数,不依赖外部

打包工具

gulp

基于流的打包工具,可以自动执行指定的任务 定义的是构建前端的流程,给图片压缩、文件压缩合并、启动server、版本控制等一系列任务设置顺序。

webpack

模块化打包工具,提供一个核心,通过loader(加载器)和plugins(插件)扩展核心,对资源进行处理,打包成符合生产环境部署的前端资源。可以将所有资源视为模块,如图片、视频、AMD模块、ES6模块、CSS等等,给模块设置依赖关系和规则实现打包,可以实现模块的按需加载。 动态import:import()是一个类函数的用法,但不是真正的函数,返回一个promise。编译时时遇到import(),webpack会对代码进行分割,生成一个chunk,再进行babel转换,运行时需要用到才会加载对应的chunk文件。可以通过命名chunk将不同的组件代码打包到同一个chunk中。

loader和plugin

loader主要负责转换代码,例如sass-loader、babel-loader、file-loader、url-loader等等

plugin主要负责处理代码,混淆、压缩、分包、提高效率等等,挂载wepack事件钩子触发,返回一个构造函数,例如html-webpack-plugin、UglifyJsPlugin(混淆)、HotModuleReplacementPlugin(热更新)等等

编译流程

流程:参数解析 -> 入口文件 -> loader编译 -> -> -> 输出

  • Compiler类,全局只会有一个它的实例,包含完整的webpack配置,负责文件的监听和编译的启动。

  • Compilation类,开发模式每一次文件变更就会生成一个新的Compilation实例,包含当前模块资源、生成的资源、变化的文件等

    ezgif-7-8e65bfc6de58

配置

懒加载

本来webpack只负责预编译模块,生成浏览器可以识别的代码;后来通过plugins也实现了gulp的代码处理工作。

CI/CD

通过定义pipeline,其中设置多个job来配置

CI - 持续集成

提交代码合并入主分支前,自动进行一系列测试、打包构建等工作

CD - 持续部署

在集成了基础上,自动实现将代码部署到生产环境

CDN

无CDN,前端资源均存储在服务器上,访问用户多了服务器压力就会很大;而且不同地域访问收到网络速度的显示体验会相差很大。利用CDN(内容分发网络)将资源缓存到距离用户最近的运营商CDN节点中,就可以解决以上问题。

路由

后端路由

后端解析url,根据服务器配置,返回html、json、图片等,浏览器通过返回的 Content-Type 来决定如何解析数据

前端路由

解析url,动态加载页面内容,

hash模式

修改url,会发送请求,造成页面的刷新,但是修改url的hash就不会,修改hash会触发hashchange事件,监听事件就可以实现动态加载页面内容了

history模式

HTML5标准支持通过pushstate和replacestate修改url不会发送请求触发页面刷新,通过popstate事件监听实现动态加载页面内容 缺点:用户手动刷新页面会发送请求,可通过后端配置所有url重定向到根页面解决

Vue

实现原理

MVVM框架,核心是双向数据绑定实现。采用数据劫持结合发布订阅模式,通过Object.defineProperty()劫持数据对象属性的getter和setter监听数据的变更,发布给订阅者触发对应的回调。包括Observer数据监听、Compile指令解析、Watcher观察器。

虚拟DOM

浏览器渲染引擎工作流程:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting 虚拟DOM就是将真实的DOM用js模拟,然后将DOM变更的对比,放到js层面去做,将多次更新合并为一次,并且只更新发生了变更的节点,最大限度的减少DOM操作,从而显著的提高性能

  • 手动操作DOM元素,每次操作都需要重新创建DOM树,非常浪费性能。
  • 虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,再通过JS一次性attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量 Vue的diff算法和React的diff算法不同
  • Vue只对同一层的元素进行比对,复杂度较低

对象根属性添加

vue对data对象的响应式处理是在实例初始化时进行的,手动添加data对象的属性就无法修改其setter和getter 解决方案:提前声明属性;Vue.set()/Vm.$set()

数组变更检测

产生原因:Vue只对数组本身进行了相应式处理,不对内容处理,直接修改数组某一项的值或者length,Vue无法劫持数据,所以无法触发更新 解决方案:使用Vue.set()或者vm.$set(),或者变异方法

3.0更新

基于Proxy的观察者,取代了基于Object.defineProperty的观察者,优点:

  • 可以检测属性的新增和删除
  • 可以坚持数组索引的变化和length的变化
  • 支持Map、Set等 重写了虚拟DOM,提速100%

React

热更新

原理:

ES6

CSS

浏览器渲染

构建DOM树,html解析 -> style rules,生成样式表 -> 构建render树,关联DOM和样式 -> 布局layout,确定坐标 -> 绘制

  • html解析、css构建、render树构建三者是交叉执行的,一边加载一边解析一边渲染
  • 每操作一次DOM就需要从头来一遍,所以开销较大

盒模型

CSS盒模型本质上是一个盒子,所有html元素都是盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容

  • IE盒模型,width/height包含content、border、padding
  • W3C标准盒模型,width/height只包含content 可以通过设置box-sizing进行切换,border-box为IE盒模型,content-box为标准盒模型

块元素/行内元素

  • 块元素新起一行,宽度默认100%;行内元素和其他元素在一行
  • 块元素可以包含其他块元素/行内元素;行内元素只能包含文本/行内元素
  • 块元素中高度、行高、上下边距等可以控制;行内元素不可以

文档流

常规流、浮动流、绝对定位流 常规流 格式化上下文,一块渲染区域,决定子元素的布局以及和其他元素的关系

  • position为static/relative,并且float为none时,触发常规流
  • 行内格式化上下文,从左到后排列
  • 块级格式化上下文,从上到下排列 浮动流
  • float不为none触发浮动
  • 浮动元素会影响行内格式化上下文,让行内元素围绕在浮动元素周围,撑大父元素,设置clear可以清除 绝对定位流
  • position为absolute/fixed,触发绝对定位流
  • 盒从常规流中移除,根据left、right、top、bottom进行定位
  • absolute相对于上级元素中最近的一个relative、fixed、absolute的元素进行定位
  • fixed相对于viewport进行定位,一些让父级元素形成堆叠上下文的属性会导致相对于父级元素定位

块级格式化上下文(BFC - Block Formatting Context)

一块独立的渲染区域,其中元素布局不受外界影响 触发方式:

  • 根元素
  • 浮动元素
  • overflow为auto、scroll、hidden时,不为visible
  • display值为inline-block、flex、inline-flex、table、inline-table、table-cell、grid、inline-grid
  • 绝对定位absolute、fixed
  • display: flow-root,无副作用,还不是完全支持 约束规则:
  • 子元素从上到下一个接一个排列,各自占一行
  • 相邻两个子元素会外边距折叠(两正取最大/两负取最小/正负取和);两个BFC之间不会折叠
  • 不会与float元素区域重叠
  • 计算BFC高度时,float元素也参与计算 作用:
  • 防止被float元素覆盖
  • 包含浮动元素
  • 第一个/最后一个子元素与非BFC父元素之间如果没有border/padding,margin将会溢出父元素,BFC可以解决
  • 多列布局防止最后一个元素因为宽度四舍五入换行

清除浮动

浮动元素会脱离常规文档流,不影响它前面的元素,它后面的行内元素会围绕浮动元素,将其包含在其中

  • clear: both; 让元素周围都没有浮动元素,就会在浮动元素下方,可以间接达到撑开父元素的作用(父元素必须包含子元素) 常说的清除浮动其实是让浮动元素撑开父元素高度的意思,浮动元素会造成父元素高度塌陷,超出父元素的浮动元素会影响父级元素的兄弟元素的布局
  • 父元素最后插入一个清除浮动的空元素/伪元素可以达到清除浮动同时撑开父元素高度的目的
  • 父元素BFC,利用BFC会包含浮动元素的特性撑开父元素

垂直居中

  • 已知高度,top: calc(50% - height/2);、top: 50%; margin-top: -height/2;、top: 0;bottom: 0;margin: auto;
  • 高度未知,top: 50%; transform: translateY(-50%)
  • 单行文本,line-height: 行高;
  • 父元素为行内元素/table,vertical-align: middle;
  • 父元素不要求高度,父元素padding: 100px 0;
  • 图片,父元素设置line-height,图片设置vertical-align: middle;

像素、分辨率、缩放比

0%