前端知识点总结
2020-02-22
目录
JavaScript
HTML+CSS
Development
Frame
Supplementary
Algorithms
JavaScript
this
创建对象
工厂模式
优点:快速创建多个类似对象
缺点:无法识别对象的类型
// 工厂模式
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayHi = function () {
alert('Into the unknow!');
};
return o;
}
var p1 = createPerson('Elsa', 24, 'queen');
p1.sayHi(); // 'Into the unknow!'
构造函数模式
优点:可以将实例标识为特定的类型
缺点:浪费资源,创造同名不相等的函数
// 构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayHi = function () {
alert('Hi! I\'m ' + this.name);
};
}
var p1 = new Person('Elsa', 24, 'queen'),
p2 = new Person('Anna', 21, 'princess');
alert(p1 instanceof Person); // true
alert(p2 instanceof Person); // true
alert(p1.sayHi == p2.sayHi); // false
原型模式
优点:节约资源,包含在原型对象上的属性和方法可以被同类型的实例访问
缺点:不同实例对原型上引用类型的属性进行修改,造成意外结果
// 原型模式
function Person() {}
Person.prototype.name = 'Elsa';
Person.prototype.age = 24;
Person.prototype.job = 'queen';
Person.prototype.sayHi = function () {
alert('Into the unknow!');
};
var p1 = new Person(),
p2 = new Person();
p1.sayHi(); // 'Into the unknow!'
alert(p1.sayHi == p2.sayHi); // true
构造函数模式+原型模式
是当前最常用的创建对象的模式,结合了构造函数模式和原型模式的优点。
// 构造函数模式+原型模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype.sayHi = function () {
alert('Hi! I\'m ' + this.name);
};
// 或者采用以下对象字面量的写法
Person.prototype = {
constructor: Person, // 此行必须!否则可能导致实例类型归属有误
sayHi: function () {
alert('Hi! I\'m ' + this.name);
}
};
var p1 = new Person('Elsa', 24, 'queen'),
p2 = new Person('Anna', 21, 'princess');
alert(p1 instanceof Person); // true
alert(p2 instanceof Person); // true
alert(p1.sayHi == p2.sayHi); // true
以下模式都是在特定情况下对工厂模式、构造函数模式、原型模式的结合和优化。
寄生构造函数模式非常像工厂模式,但是调用方法类似构造函数模式。
一般构造函数在不返回值的情况下默认返回新对象实例,若在构造函数添加return,则可以改写返回值。
// 寄生构造函数模式
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayHi = function () {
alert('Into the unknow!');
};
return o; // 改写构造函数返回的对象实例
}
var p1 = new Person('Elsa', 24, 'queen');
p1.sayHi(); // 'Into the unknow!'
这个模式常用来为,内置对象创建自定义对象构造函数。
// 实际运用
function SpecialArray() {
var values = new Array();
values.push.apply(values, arguments);
values.toPipedString = function() {
return this.join('|');
};
return values;
}
var colors = new SpecialArray('red', 'blue', 'yellow');
alert(colors.toPipedString()); // 'red|blue|yellow'
稳妥对象:没有公共属性且其方法不引用this的对象。
稳妥构造函数模式使得除了调用sayHi()外没有别的方式访问其数据成员。
该模式提供的安全性,使得它非常适合在某些安全执行环境下使用。
// 稳妥构造函数模式
function Person(name, age, job) {
var o = new Object();
o.sayHi = function () {
alert(name);
};
return o;
}
var p1 = Person('Elsa', 24, 'queen');
p1.sayHi(); // 'Elsa'
使用动态原型模式时,不能使用对象字面量重新改写原型!
// 动态原型模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayHi != 'function') {
Person.prototype.sayHi = function () {
alert('Hi! I\'m ' + this.name);
};
}
}
var p1 = Person('Elsa', 24, 'queen');
p1.sayHi(); // "Hi! I'm Elsa"
继承
原型链继承
优点:具有清晰的继承关系,实例与其继承的对象类型可以通过instanceof进行判断
缺点:
- 包含引用类型属性的原型被其实例共有,可能造成意外修改
- 创建子类型的实例时,无法向超类型的构造函数传递参数
// 原型链继承
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subProperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subProperty;
}
var instance = new SubType();
alert(instance.getSuperValue()); // true
alert(instance instanceof SuperType); // true
alert(instance instanceof SubType); // true
借用构造函数继承
优点:可以在子类型构造函数中向超类型构造函数传递参数
缺点:在超类型的原型中定义的方法,对子类型而言不可见
// 借用构造函数继承
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
var instance = new SubType('Elsa', 24);
alert(instance.name); // 'Elsa'
alert(instance.colors); // 'red,blue,yellow'
组合继承
优点:结合了原型链继承和借用构造函数继承的优势,互补了它们的缺点,是最常用的继承方法
缺点:浪费资源,在子类型的实例和原型对象上都有同样的超类型的实例属性
// 组合继承
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; // 更正子类型的构造函数指向
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType('Elsa', 24);
instance1.colors.push('green');
alert(instance1.colors); // 'red,blue,yellow,green'
var instance2 = new SubType('Anna', 21);
alert(instance2.colors); // 'red,blue,yellow'
原型式继承
优点:若只是需要某对象与另一对象保持相似,可以不必要创建构造函数、原型对象
缺点:可能造成多个实例对原型对象上引用类型属性的意外修改
// 原型式继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: 'Elsa',
friends: ['Anna', 'Olaf', 'Nokk']
};
var anotherPerson = object(person);
anotherPerson.name = 'Anna';
anotherPerson.friends.push('Seven');
alert(anotherPerson.name); // 'Anna'
alert(person.friends); // 'Anna,Olaf,Nokk,Seven'
ECMAScript5新增Object.create()方法规范了原型式继承。
// 调用Object.create()进行继承
var person = {
name: 'Elsa',
friends: ['Anna', 'Olaf', 'Nokk']
};
var anotherPerson = Object.create(person);
anotherPerson.name = 'Anna';
anotherPerson.friends.push('Seven');
alert(anotherPerson.name); // 'Anna'
alert(person.friends); // 'Anna,Olaf,Nokk,Seven'
寄生式继承
优点:若只是需要某对象与另一对象保持相似,可以不必要创建构造函数、原型对象
缺点:无法函数复用,导致资源浪费
// 寄生式继承
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
alert('Into the unknow!');
};
return clone;
}
var person = {
name: 'Elsa',
friends: ['Anna', 'Olaf', 'Nokk']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'Into the unknow!'
寄生组合式继承
优点:克服了组合继承实例和原型对象上重复实例属性的问题,是最理想的继承实现
// 寄生组合式继承
function inheritPrototype(subType, superType) {
var prototype = object(superType); // 创建对象
prototype.constructor = subType; // 指向正确的constructor
subType.prototype = prototype; // 指定原型对象
}
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// 确定原型链
inheritPrototype(SubType, SupperType);
SubType.prototype.sayAge = function() {
alert(this.age);
};
执行环境与作用域
执行环境定义了变量或函数的有权访问的其他数据,决定了它们各自的行为。
每个函数都有自己的执行环境。
每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的前端,始终是当前执行的代码所在环境的变量对象。
下一个变量对象是外部环境,一直延伸到全局执行环境。
标识符解析是沿着作用域一级一级搜索的过程,始终从作用域链前端开始,逐级向后,直到找到标识符为止。
如果找不到标识符,通常会导致错误发生。
闭包
function outer(name) {
return function () {
console.log(name);
}
}
outer('vivi')(); // vivi
在outer函数执行完后,其活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
因此称这个匿名函数为闭包。
递归
// Description
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps.
In how many distinct ways can you climb to the top?
// Solution
const recurse = function f(n) {
if(n == 0) return 0;
if(n == 1) return 1;
if(n == 2) return 2;
return f(n - 1) + f(n - 2);
};
recurse(5); // 8
MVC、MVP、MVVM
HTML+CSS
左右布局
左右等宽
// HTML
<div class="wrapper">
<div class="left">aaaaaa</div>
<div class="right">bbbbb</div>
</div>
// float实现
.wrapper {
overflow: hidden;
}
.left {
float: left;
width: 50%;
height: 100px;
background-color: red;
}
.right {
float: left;
width: 50%;
height: 100px;
background-color: blue;
}
左侧固定宽度,右侧自适应
// HTML
<div class="wrapper">
<div class="left">aaaaaa</div>
<div class="right">bbbbb</div>
</div>
// CSS
.wrapper {
overflow: hidden;
}
.left {
float: left;
width: 100px;
height: 100px;
background-color: red;
}
.right {
width: auto; /* 可以不设置,默认为auto */
height: 100px;
background-color: blue;
}
右侧固定宽度,左侧自适应
// HTML
<div class="wrapper">
<div class="right">bbbbb</div>
<div class="left">aaaaaa</div>
</div>
// CSS
.wrapper {
overflow: hidden;
}
.left {
width: auto;
height: 100px;
background-color: red;
}
.right {
float: right;
width: 100px;
height: 100px;
background-color: blue;
}
圣杯布局
// HTML
<div class="wrapper">
<div class="middle">aaaaaaa</div>
<div class="left">bbbbb</div>
<div class="right">cccc</div>
</div>
// CSS
// padding-left为.left框的宽度
// padding-right为.right框的宽度
.wrapper {
padding: 0 100px;
overflow: hidden;
}
.middle {
float: left;
width: 100%;
height: 100px;
background: yellow;
}
// position: relative;相对于自身进行定位
// left的距离为负的.left框的宽度
.left {
position: relative;
left: -100px;
float: left;
width: 100px;
height: 100px;
margin-left: -100%;
background: red;
}
// right的距离为负的.right框的宽度
.right {
position: relative;
right: -100px;
float: left;
width: 100px;
height: 100px;
margin-left: -100px;
background: blue;
}
双飞翼布局
// HTML
// 注意.middle框要放在.left和.right框的前面渲染
<div class="wrapper">
<div class="middle">
<div class="inner">aaaaaaa</div>
</div>
<div class="left">bbbbb</div>
<div class="right">cccc</div>
</div>
// CSS
.wrapper {
overflow: hidden;
}
.middle {
float: left;
width: 100%;
height: 100px;
background: yellow;
}
// 使用margin将左右两个框的宽度隔开,防止内容遮挡
.inner {
margin-left: 100px;
margin-right: 100px;
word-break: break-all;
}
.left {
float: left;
width: 100px;
height: 100px;
margin-left: -100%; // 将.left框上移一行
background: red;
}
.right {
float: left;
width: 100px;
height: 100px;
margin-left: -100px; // 将.right框上移一个自身的宽度
background: blue;
}
水平垂直居中
display: flex实现
// HTML
<div class="outer">
<div class="inner"></div>
</div>
// CSS
.outer {
display: flex;
justify-content: center;
align-items: center;
width: 500px;
height: 500px;
background: red;
}
.inner {
width: 100px;
height: 100px;
background: yellow;
}
变形
过渡
动画
画布
层叠顺序
Development
Webpack
Gulp
Git
Frame
Vue
React
Vue和React
Sass
Less
小程序
jQuery
Bootstrap
Supplementary
NodeJs
i18Next
Echarts
Algorithms
数组
数组是顺序存储结构,拥有非常高效的随机访问能力;至于数组的劣势体现在插入和删除元素方面。
数组适合的是 读操作多、写操作少 的场景。
链表
链表是一种在物理上非连续、非顺序的数据结构,由若干节点组成。
链表的优势在于灵活地进行插入和删除操作。
数组和链表是物理结构上的存储结构。与之相对的,则是数据存储的逻辑结构。
线性逻辑结构:顺序表、栈、队列
非线性逻辑结构:树、图
栈
栈中的元素只能先进后出。最早进入到元素存放的位置称为栈底,最后进入的元素位置称为栈顶。
栈既可以用数组实现,也可以用链表实现。
队列
队列中的元素只能先进先出。最早进入到元素存放的位置称为队头,最后进入的元素位置称为队尾。
为方便入队操作,把队尾位置规定为最后入队元素的下一个位置。
用数组实现的队列可以采用循环队列的方式维持队列容量的恒定。
一直到 (队尾下标 + 1) % 队列长度 == 队头下标 时,代表队列已满。
散列表
散列表也叫哈希表,这种数据结构提供了键(Key)和值(Value)的映射关系。
通过哈希函数将key转为下标存放value到数组中,实现读取操作的时间复杂度为O(1)。
解决哈希冲突的方法主要有两种:开放寻址和链表法。
开放寻址法,即当前对应的数组下标被占用时,寻找下一个空位。
链表法,即数组中的元素不仅是一个对象,还是一个链表的头节点。当映射发生冲突时,插入到链表中即可。
散列表扩容:
1、扩容,变为原来数组长度的两倍。
2、重新计算hash。因为长度扩大后,hash规则也发生了改版。
树
树是n(n >= 0)个节点的有限集合。当n = 0时,称为空树。在任意一个非空树种,有如下特点:
1、有且仅有一个特定的称为根的节点。
2、当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,称为根的子树。
二叉树
// 二叉树
二叉树是树的一种特殊形式。这种书的每个节点最多有2个节点。
二叉树节点的两个孩子节点,被称为左孩子和右孩子。孩子节点的顺序是固定的,不能混淆颠倒。
// 满二叉树
一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级,则称为满二叉树。
// 完全二叉树
对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为1到n;
如果这个树所有节点和同样深度的满二叉树的编号从1到n的节点位置相同,则成为完全二叉树。
二叉查找树
- 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
- 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
- 左、右子树也都是二叉查找树
// 节点类
class TreeNode {
constructor(data) {
this.data = data;
this.leftChild = null;
this.rightChild = null;
}
}
// 二叉树
class BinaryTree {
constructor(arr) {
if(!Array.isArray(arr) || arr.length <= 0)
throw Error('BinaryTree Init went wrong!');
else
this.root = this.createBinaryTree(arr);
}
createBinaryTree(arr) {
// 如果没有节点了,则返回null
if(arr.length <= 0) return null;
var node = null,
data = arr.shift();
if(data) {
node = new TreeNode(data);
node.leftChild = this.createBinaryTree(arr);
node.rightChild = this.createBinaryTree(arr);
}
return node;
}
// 前序遍历
prevOrder(node) {
if(!node) return;
console.log(node.data);
this.prevOrder(node.leftChild);
this.prevOrder(node.rightChild);
}
// 中序遍历
inOrder(node) {
if(!node) return;
this.inOrder(node.leftChild);
console.log(node.data);
this.inOrder(node.rightChild);
}
// 后序遍历
postOrder(node) {
if(!node) return;
this.postOrder(node.leftChild);
this.postOrder(node.rightChild);
console.log(node.data);
}
// 层序遍历
levelOrder() {
var queue = [];
queue.push(this.root);
while(queue.length > 0) {
var node = queue.shift();
console.log(node.data);
if(node.leftChild) {
queue.push(node.leftChild);
}
if(node.rightChild) {
queue.push(node.rightChild);
}
}
}
}
var bt = new BinaryTree([3, 2, 9, null, null, 10, null, null, 8, null, 4]);
bt.prevOrder(bt.root); // 3 2 9 10 8 4
bt.inOrder(bt.root); // 9 2 10 3 8 4
bt.postOrder(bt.root); // 9 10 2 4 8 3
bt.levelOrder(); // 3 2 8 9 10 4
二分查找
function binarySearch(arr, target) {
var start = 0,
end = arr.length - 1;
while(start <= end) {
var mid = parseInt((start + end) / 2),
cur = arr[mid];
if(cur == target) {
return mid;
} else if(cur > target) {
end = mid - 1;
} else if(cur < target) {
start = mid + 1;
}
}
return -1;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
binarySearch(arr, 4); // 3
冒泡排序
function bubbleSort(arr) {
for(let i = 0; i < arr.length - 1; i ++) {
for(let j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j + 1]) {
var [prev, next] = [arr[j], arr[j + 1]];
arr[j] = next;
arr[j + 1] = prev;
}
}
}
}
var arr = [5, 8, 9, 3, 6, 4, 2, 1, 7];
bubbleSort(arr);
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
快速排序
function quickSort(arr, start, end) {
if(start >= end) return;
// 得到基准元素的位置
var pivot = partition(arr, start, end);
// 相对基准元素,分成两部分进行排序
quickSort(arr, start, pivot - 1);
quickSort(arr, pivot + 1, end);
}
function partition(arr, start, end) {
var pivot = arr[start],
mark = start;
for(let i = start + 1; i <= end; i++) {
// 若当前元素小于基准元素,则将mark右移一位,交换它们的位置
if(arr[i] < pivot) {
mark++;
var tmp = arr[mark];
arr[mark] = arr[i];
arr[i] = tmp;
}
}
// 交换基准元素和起始元素的位置
arr[start] = arr[mark];
arr[mark] = pivot;
// 返回基准元素下标
return mark;
}
var arr = [5, 8, 9, 3, 6, 4, 2, 1, 7];
quickSort(arr, 0, arr.length - 1);
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9]