前端知识点总结

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进行判断
缺点:

  1. 包含引用类型属性的原型被其实例共有,可能造成意外修改
  2. 创建子类型的实例时,无法向超类型的构造函数传递参数
// 原型链继承
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的节点位置相同,则成为完全二叉树。

二叉查找树

  1. 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
  2. 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
  3. 左、右子树也都是二叉查找树
// 节点类
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]