链表-排序-栈-队列-图-模式-MVVM-Three.js-TypeScript

**

链表-排序-栈-队列-图-模式-MVVM-Three.js-TypeScript

**

  • Get off my back. 少跟我罗嗦

  • Who do you think you are?你以为你是哪根葱?


  • Just look at what you’ve done! 看看你都做了些什么

  • I’ll never forgive you! 我永远都不会饶恕你

  • You’re a disgrace. 你真丢人

  • You’ll be sorry. 你会后悔的

  • Don’t push me ! 别逼我!

  • You asked for it. 你自找的

  • You are dead meat. 你死定了

  • You have a lot of nerve. 脸皮真厚

  • You’re a joke! 你真是一个小丑

  • Get over yourself.别自以为是

  • What a stupid idiot! 真是白痴一个

  • I hate you!我讨厌你

ts: http://note.youdao.com/noteshare?id=edc82728ff496f2778a6577b815d7ad7

3D: http://www.yanhuangxueyuan.com/

More stupid than a pig比猪还笨

1、链表-单链表-双链表-循环链表.md

一、链表数据结构

链表存储有序的元素的集合,但不同于数组,链表中的元素在内存中并不是连续放置的。

链表的组成:存储元素本身的节点和一个指向下一个元素的引用(指针或链接)。

img img

二、使用数组和链表的优缺点
数据结构优点缺点
数组可以快速的通过下标查找元素删除和插入元素的成本高
链表可以快速插入和删除元素查找元素必须从头开始查找
三、创建单向链表类
方法说明
push()向链表的末尾添加一个节点
removeAt(index)删除指定下标的元素,返回删除的元素
insert(element,index)在指定位置插入元素
indexOf(element)查找下标,不存在返回-1
remove(element)删除元素
size()获取链表个数
isEmpty()获取链表是否为空
getHead()获取首元素

代码实现

//节点类
class Node{
    constructor(element){
        this.element = element;
        this.next = null;
    }
}

//创建单向链表类
class LinkedList{
    constructor(){
        this.head = null;   //链表头
        this.count = 0;     //记录节点数量
    }

    //在链表的末尾添加
    push(element){
        let node = new Node(element);
        if(this.head === null){
            //链表为空
            this.head = node;
        }else{
            let current = this.head;
            while(current.next != null){
                current = current.next;
            }
            current.next = node;
        }
        this.count++;
    }

    getElementAt(index){
        let current = this.head;
        for(let i = 0;i < index;i++){
            current = current.next;
        }
        return current
    }

    //删除
    removeAt(index){
        if(index >= 0 && index < this.count){
            //1.链表开头删除
            let current = this.head;
            if(index === 0){
                this.head = current.next;
            }else{
                let prev = this.getElementAt(index - 1);
                current = prev.next;
                prev.next = current.next;
            }
            this.count--;
            return current.element
        }else{
            return undefined
        }
    }

    //插入
    insert(element,index){
        if(index >= 0 && index <= this.count){
            let node = new Node(element);
            //1.是否是在开头插入
            if(index === 0){
                if(this.head === null){
                    this.head = node;
                }else{
                    let current = this.head;
                    this.head = node;
                    node.next = current;
                }
            }else{
                let prev = this.getElementAt(index - 1);
                let current = prev.next;
                prev.next = node;
                node.next = current;
            }

            this.count++;
        }else{
            return false
        }
    }
}

let l = new LinkedList();

l.push('a');

l.push('b');

// console.log(l.removeAt(1));

l.insert('c',2);

console.log(l);
四、双向链表

img

五、创建双向链表类
方法说明
push()向链表的末尾添加一个节点
removeAt(index)删除指定下标的元素,返回删除的元素
insert(element,index)在指定位置插入元素
indexOf(element)查找下标,不存在返回-1
remove(element)删除元素
size()获取链表个数
isEmpty()获取链表是否为空
getHead()获取首元素

代码实现

class Node{
    constructor(element){
        this.element = element;
        this.prev = null;
        this.next = null;
    }
}

class DoubleList{
    constructor(){
        this.head = null;  //记录头元素
        this.tail = null;  //记录尾元素
        this.count = 0;    //记录元素个数
    }

    //在链表结尾添加
    push(element){
        let node = new Node(element);
        //1.判断链表是否为空
        if(this.head === null){
            this.head = node;
            this.tail = node;
        }else{
            let last = this.tail;
            last.next = node;
            node.prev = last;
            this.tail = node;
        }
        this.count++;
    }

    getElementAt(index){
        let current = this.head;
        for(let i = 0;i < index;i++){
            current = current.next;
        }
        return current
    }

    //插入
    insert(element,index){
        //1.边界值的判断
        if(index >= 0 && index <=this.count){
            console.log(this.count);
            let node = new Node(element);
            //1.在链表开头添加
            if(index === 0){
                if(this.head === null){
                    //1.1 链表为空
                    this.head = node;
                    this.tail = node;
                }else{
                    //1.2 链表不为空
                    let current = this.head;
                    node.next = current;
                    current.prev = node;
                    this.head = node;
                }
            }else if(index === this.count){
                //2.在末尾添加
                let last = this.tail;
                last.next = node;
                node.prev = last;
                this.tail = node;
            }else{
                //3.中间插入
                let current = this.getElementAt(index);
                let prev = current.prev;
                prev.next = node;
                node.prev = prev;
                node.next = current;
                current.prev = node;
            }
        }else{
            return false;
        }
    }

    //删除指定的元素,并返回该元素
    removeAt(index){
        if(index >= 0 && index < this.count){
            let current = this.head;
            if(index === 0){
                //1.从开头删除
                if(this.count === 1){
                    //1.1 只有一个元素
                    this.head = null;
                    this.tail = null;
                }else{
                    //1.2 有多个元素
                    this.head = current.next;
                    current.next.prev = null;
                }
            }else if(index === (this.count-1)){
                //2.从末尾删除
                current = this.tail;
                this.tail = current.prev;
                this.tail.next = null;
            }else{
                //3.从中间删除
                current = this.getElementAt(index);
                current.prev.next = current.next;
                current.next.prev = current.prev;
            }
            this.count--;
            return current.element
        }else{
            return undefined
        }
    }

    //返回头元素
    getHead(){
        return this.head;
    }

    //返回尾元素
    getTail(){
        return this.tail;
    }

    //返回链表的个数
    size(){
        return this.count;
    }

    //是否为空
    isEmpty(){
        return this.count === 0;
    }

    //清空列表
    clear(){
        this.head = null;
        this.tail = null;
        this.count = 0;
    }

    //查找下标
    indexOf(element){
        let current = this.head;
        for(let i = 0;i<this.count;i++){
            if(element === current.element){
                return i
            }
            current = current.next;
        }
        return -1
    }

    //删除元素
    remove(element){
        let index = this.indexOf(element);
        this.removeAt(index)
    }   

    //打印
    print(){
        let current = this.head;
        for(let i = 0;i<this.count;i++){
            console.log(current);
            current = current.next;
        }
    }
}

let d = new DoubleList();
d.push('a');
d.push('b');
d.push('c');
// d.insert('c',1);
// d.removeAt(1);
// console.log(d);
d.print();
六 循环链表

img

案例

img

代码实现
class Node{
    constructor(element) {
        this.element = element;
        this.next = null;
    }   
}

class CircleList{
    constructor(){
        this.head = null; //头元素
        this.count = 0; //记录一共多少人
    }

    getElementAt(index){
        let current = this.head;
        for(let i = 0;i < index;i++){
            current = current.next;
        }
        return current
    }

    //在末尾添加
    push(element){
        let node = new Node(element);
        //1.链表为空
        if(this.head === null){
            this.head = node;
            node.next = this.head;
        }else{
            //2.不为空
            let last = this.getElementAt(this.count-1);
            last.next = node;
            node.next = this.head;
        }
        this.count++;
    }

    /**
     * 
     * @param {number} n 总人数
     * @param {number} k 报数上限
     */
    ysfCircle(n,k){  //9
        for(let i = 1;i<=n;i++){
            this.push(i);
        }
        let helper = this.head; //记录上一个
        let current = this.head;//删除的元素

        while(helper.next != this.head){  //把helper移动到最后一个元素
            helper = helper.next;
        }

        while(this.count > 15){
            for(let i = 1;i<k;i++){
                helper = helper.next;
                current = current.next;
            }
            console.log("要淘汰的人",current.element)
            helper.next = current.next;
            current = current.next;
            this.count--;
        }
    }
}

let c = new CircleList();

c.ysfCircle(30,9)

// c.push('a');

// c.push('b');

// console.log(c);

2、栈.md

栈数据结构

栈是一种遵从后进先出(LIFO)原则的有序集合。
在这里插入图片描述

创建栈

方法说明
push添加元素
pop移除栈顶的元素
peek返回栈顶的元素
isEmpty如果栈里没有任何元素就返回true
clear移除栈里的所有元素
size返回栈里的元素个数
toString转字符串

案例 十进制转二进制
在这里插入图片描述

代码实现
class Stack{
    constructor(){
        this.items = {};//放置元素
        this.count = 0; //元素的个数
    }

    //添加元素
    push(element){
        this.items[this.count] = element;
        this.count++;
    }

    //最后一个元素
    pop(){
        if(this.isEmpty()){
            return undefined;
        }
        let top = this.items[this.count-1];
        delete this.items[this.count-1];
        this.count--;
        return top
    }

    //peek  返回栈顶的元素
    peek(){
        return this.items[this.count-1]
    }

    //判断栈是否为空
    isEmpty(){
        return this.count === 0
    }

    //清空栈
    clear(){
        this.items = {};
        this.count = 0;
    }

    //获取栈内元素的个数
    size(){
        return this.count;
    }

    //转成字符串
    toString(){
        if(this.isEmpty()){
            return ''
        }
        let str = this.items[0];
        for(let i = 1;i<this.count;i++){
            str = `${str},${this.items[i]}`
        }
        return str
    }
}

// let obj = {};

// obj.xxx = xxx;

// let s = new Stack();

// s.push('a');

// s.push('b');

// // console.log(s.pop());

// console.log(s.toString());

//十进制转二进制
function decimalToBinary(number){
    let s = new Stack();
    let rem = ''; //余数
    let str = '';
    while(number > 0){
        rem = Math.floor(number%2);
        s.push(rem);
        number = Math.floor(number/2);
    }

    let count = s.size();
    for(let i = 0;i<count;i++){
        str += `${s.pop()}`
    }
    return str
}

console.log(decimalToBinary(10))

3、队列.md

队列数据结构

队列是遵循先进先出(FIFO,也称先来先服务)原则的一组有序的集合。
在这里插入图片描述

创建队列

方法说明
enqueue向队列尾部添加一项
dequeue移除队列的第一项并返回被移除的元素
peek返回队列中的第一个元素
isEmpty如果队列中不包含任何元素,返回true,否则返回false
size返回队列包含的元素个数
toString转成字符串
clear清空队列

案例 约瑟夫环 丢手绢 击鼓传花

代码实现
class Queue{
    constructor(){
        this.items = {}; //队列的元素
        this.count = 0;  //记录键
        this.lowestCount = 0; //删除元素键
    }

    //入队
    enqueue(element){
        this.items[this.count] = element;
        this.count++;
    }

    //删除
    dequeue(){
        let target = this.items[this.lowestCount];
        delete this.items[this.lowestCount];
        this.lowestCount++;
        return target
    }

    //获取队列的元素个数
    size(){
        return this.count - this.lowestCount
    }

    //判断队列是否为空
    isEmpty(){
        return this.count - this.lowestCount === 0
    }

    //返回第一个元素
    peek(){
        return this.items[this.lowestCount];
    }

    //清空队列
    clear(){
        this.items = {};
        this.count = 0;
        this.lowestCount = 0;
    }

    //转字符串
    toString(){
        if(this.isEmpty()){
            return ''
        }
        let str = this.items[this.lowestCount];

        for(let i = this.lowestCount+1;i<this.count;i++){
            str = `${str},${this.items[i]}`;
        }

        return str
    }
}

// let d = new Queue();

// d.enqueue('a');

// d.enqueue('b');

// d.enqueue('c');

// d.enqueue('d');

// d.dequeue();

// console.log(d.toString());

//击鼓传花
function flower(member,num){
    let d = new Queue();
    let list = [];
    //1.入队
    for(let i = 0;i<member.length;i++){
        d.enqueue(member[i]);
    }

    //2.报数删除 保留一个人
    while(d.size() > 1){
        for(let j = 1;j<num;j++){
            d.enqueue(d.dequeue()) //
        }
        list.push(d.dequeue())
    }

    let reduce = d.dequeue();

    return {
        reduce,
        list
    }
}

console.log(flower(['a','b','c','d'],3))

4、图结构.md

图结构

图是一种和树有些相似的数据结构。

  • 实际上,在数学的概念上,树是图的一种。 我们知道树可以用来模拟很多现实的数据结构,比如:家谱/公司组织架构等等。
  • 什么场景使用图来模拟更合适呢?

人与人之间的关系网
在这里插入图片描述

互联网中的网络关系
在这里插入图片描述

村庄间的关系网
在这里插入图片描述

北京地铁图
在这里插入图片描述

图是什么?

我们会发现,上面的结点(其实图中叫顶点Vertex)之间的关系,是不能使用树来表示(几叉树都不可以)
图的特点?

  • 一组顶点:通常用V(Vertex)表示顶点的集合

  • 一组边:通常用E(Edge)表示边的集合

    边是顶点和顶点之间的连线

    边可以是有向的,也可以是无向的。(比如A—B, 通常表示无向,A—>B 通常表示有向)
    在这里插入图片描述

图的术语

  • 顶点:表示图中的一个结点
  • 边:顶点和顶点之间的连线
  • 相邻顶点:由一条边连接在一起的顶点称为相邻顶点
  • 度:一个顶点的度是相邻顶点的数量

路径:

  • 无向图:图中的边是没有方向的
  • 有向图:图中的边是有方向的
  • 无权图和带权图
  • 图的表示方式:邻接矩阵和邻接表

邻接矩阵
在这里插入图片描述

邻接表
在这里插入图片描述

创建图

方法说明
addVertex添加节点
addEdge增加邻接表中的边
getVertices获取顶点列表
getAdjList返回一个邻接表
toString转成字符串
代码实现
class Graph{
    constructor(isDirected=false){
        this.isDirected = isDirected; //代表是否有向
        this.vertices = []; //使用数组存储所有顶点的名字
        this.adjList = new Map(); //定义邻接表
    }

    //增加新顶点
    addVertex(v){
        if(!this.vertices.includes(v)){
            this.vertices.push(v);
            this.adjList.set(v,[]);
        }
    }

    //添加边
    addEdge(v,w){
        if(!this.adjList.get(v)){
            this.addVertex(v);
        }

        if(!this.adjList.get(w)){
            this.addVertex(w);
        }

        this.adjList.get(v).push(w);
        if(!this.isDirected){
            this.adjList.get(w).push(v);
        }
    }

    getVertices(){
        return this.vertices
    }

    getAdjList(){
        return this.adjList
    }

    toString(){
        let str = '';

        for(let i =0;i<this.vertices.length;i++){
            str += `${this.vertices[i]}--->`;

            let list = this.adjList.get(this.vertices[i]);

            for(let j = 0;j<list.length;j++){
                str += `${list[j]} `
            }
            str += '\n';
        }

        return str
    }

}

let g = new Graph();

let myVertices = ['A','B','C','D','E','F','G','H','I'];

for(let i = 0;i<myVertices.length;i++){
    g.addVertex(myVertices[i]);
}

g.addEdge('A','B');

g.addEdge('A','C');

g.addEdge('A','D');

g.addEdge('C','D');

g.addEdge('C','G');

g.addEdge('D','G');

g.addEdge('D','H');

g.addEdge('B','E');

g.addEdge('B','F');

g.addEdge('E','I');

console.log(g.getVertices());

console.log(g.getAdjList());

console.log(g.toString());

8、树结构.md

树结构
树是一种分层数据的抽象模型。常见树的例子有家谱,公司的组织架构

在这里插入图片描述

树相关的术语
在这里插入图片描述

由上图看得出树是一些节点的集合,总结一下树的一些基本概念:

  • 节点:树中的数据元素都称之为节点
  • 根:最上面的结点称之为根,一颗树只有一个根且由根发展而来,从另外一个角度来说,每个结点都可以认为是其子树的根
  • 父亲:结点的上层结点,如图中,结点K的父亲是E、结点L的父亲是G
  • 兄弟:具有相同父亲的结点称为兄弟,图中F、G、H互为兄弟
  • 结点的度:结点所拥有的子树的个数称之为结点的度,如结点B的度为3
  • 树叶:度为0的结点,也叫作终端结点,图中D、K、F、L、H、I、J都是树叶
  • 分支结点:度不为0的结点,也叫作非终端结点或内部结点,图中根、A、B、C、E、G都是分支结点
  • 结点的层次:从根节点到树中某结点所经路径上的分支树称为该结点的层次,根节点的层次规定为1,其余结点的层次等于其父亲结点的层次+1
  • 树的深度:树中结点的最大层次数,图中树的深度为4

二叉树和二叉搜索树(BST)

二叉树中的节点最多只能有两个子节点:一个左侧子节点,一个右侧子节点。

二叉搜索树规定只允许在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值。

在这里插入图片描述

创建BinarySearchTree类
在这里插入图片描述

方法说明
insert(key)向树中插入一个新值
search(key)在树中查找一个键。如果节点存在,则返回 true;如果不存在,则返回 false。
inOrderTraverse()通过中序遍历方式遍历所有节点
preOrderTraverse()通过先序遍历方式遍历所有节点。
postOrderTraverse()通过后序遍历方式遍历所有节点。
min()返回树中最小的值/键
max()
remove(key)从树中移除某个键
代码实现
class Node{
    constructor(element){
        this.element = element;
        this.left = null;
        this.right = null;
    }
}


class Tree{
    constructor(){
        this.root = null;
    }

    insert(element){
        let node = new Node(element);
        if(this.root === null){
            this.root = node;
        }else{
            this.insertNode(node,this.root);
        }
    }

    insertNode(newNode,rootNode){
        if(newNode.element < rootNode.element){
            if(rootNode.left === null){
                rootNode.left = newNode;
            }else{
                this.insertNode(newNode,rootNode.left);
            }
        }else{
            if(rootNode.right === null){
                rootNode.right = newNode;
            }else{
                this.insertNode(newNode,rootNode.right);
            }
        }
    }

    min(){
        return this.minNode(this.root);
    }

    minNode(rootNode){
        while(rootNode.left != null){
            rootNode = rootNode.left;
        }
        return rootNode
    }

    max(){
        return this.maxNode(this.root);
    }

    maxNode(rootNode){
        while(rootNode.right != null){
            rootNode = rootNode.right;
        }
        return rootNode
    }

    search(element){
        return this.searchNode(element,this.root);
    }
    searchNode(element,rootNode){
        if(rootNode === null){
            return false;
        }

        if(element < rootNode.element){
            return this.searchNode(element,rootNode.left);
        }else if(element > rootNode.element){   
            return this.searchNode(element,rootNode.right);
        }else{
            return true
        }
    }

    inOrderTraverse(callback){
        this.inOrderTraverseNode(this.root,callback);
    }

    inOrderTraverseNode(node,callback){
        if(node != null){
            this.inOrderTraverseNode(node.left,callback);
            callback(node.element);
            this.inOrderTraverseNode(node.right,callback);
        }
    }

    preOrderTraverse(callback){
        this.inOrderTraverseNode(this.root,callback);
    }

    preOrderTraverseNode(node,callback){
        if(node != null){
            callback(node.element);
            this.preOrderTraverseNode(node.left,callback);
            this.preOrderTraverseNode(node.right,callback);
        }
    }

    postOrderTraverse(callback){
        this.postOrderTraverseNode(this.root,callback);
    }

    postOrderTraverseNode(node,callback){
        if(node != null){
            this.postOrderTraverseNode(node.left,callback);
            this.postOrderTraverseNode(node.right,callback);
            callback(node.element);
        }
    }

    remove(element){
        this.root = this.removeNode(element,this.root);
    }

    removeNode(element,node){
        if(node === null){
            return null;
        }

        if(element < node.element){
            node.left = this.removeNode(element,node.left);
            return node;
        }else if(element > node.element){
            node.right = this.removeNode(element,node.right);
            return node;
        }else{
            //删除叶子节点
            if(node.left === null && node.right === null){
                node = null;
                return node;
            }

            //删除只有一个子节点
            if(node.right === null){
                node = node.left;
                return node;
            }else if(node.left === null){
                node = node.right;
                return node;
            }

            //删除有两个子节点
            //1.查找最小子节点
            let endNode = this.minNode(node.right);
            node.element = endNode.element;
            node.right = this.removeNode(endNode.element,node.right);
            return node;
        }
    }
}

let tree = new Tree();

tree.insert(11);
tree.insert(10)
tree.insert(20);
tree.insert(8);

console.log(tree.min());
console.log(tree.max());

console.log(tree.search(11));

console.log(tree.search(19));

let callback = (element) => {
    console.log(element)
}

9、单例模式.md

**设计模式

设计模式是软件设计过程中通用的解决方案**

问题-----> 解决方案 :处理一类问题的方法

23种设计模式

单例设计模式

  • 单例设计模式特点:在整个程序运行过程中一个类只有一个实例。
  • 单例模式的使用场景:解决一个全局使用的类,频繁创建和销毁。拥有对象的唯一性,并保证内存中对象的唯一。可以节省内存,因为单例共用一个实例,有利于垃圾回收机制。
  • 常见的单例模式:回收站,弹窗
  • 单例模式一
class Person{
    constructor(){
        if(!Person.instance){
            Person.instance = this;
        }
        return Person.instance
    }
}

let p1 = new Person();

let p2 = new Person();

console.log(p1 === p2);  //true

单例模式二
class Person{
    static getInstance(){
        if(!Person.instance){
            Person.instance = new Person();
        }
        return Person.instance
    }
}

let p1 = Person.getInstance();

let p2 = Person.getInstance();

console.log(p1 === p2);

单例模式---模态框
class Model{
    static getInstance(){
        if(!Model.instance){
            Model.instance = document.createElement('div');
            Model.instance.innerHTML = '内容';
            Model.instance.id = "model";
            Model.instance.style.display = 'none';
            document.body.appendChild(Model.instance);
        }
        return Model.instance
    }
}

let open = document.querySelector('#open');
let close = document.querySelector('#close');
open.addEventListener('click',function(){
    let model = Model.getInstance();
    model.style.display = 'block';
})

close.addEventListener('click',function(){
    let model = Model.getInstance();
    model.style.display = 'none';
})

10、观察者模式(发布订阅模式).md

观察者模式,又称发布订阅模式

观察者模式(又被称为发布-订阅(public/Subscribe))模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象再状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

在这里插入图片描述

<button id="public">发布动态</button>
    <script>
        // 观察者  粉丝
        class Fan{
            constructor(name){
                this.name = name;
            }
            //接收动态
            update(){
                console.log(`${this.name}接收到变化`)
            }
        }    


        class Star{
            constructor(){
                this.fans = [];
            }
            //添加粉丝
            add(fan){
                this.fans.push(fan);
            }
            //通知粉丝
            public(){
                // [1,2,3].forEach(item =>{})
                this.fans.forEach(fan => {
                    fan.update();
                })
            }
        }

        let star = new Star();

        let f1 = new Fan('小红');
        let f2 = new Fan('小蓝');
        let f3 = new Fan('小绿');
        let f4 = new Fan('小灰');

        star.add(f1);
        star.add(f2);
        star.add(f3);
        star.add(f4);

        let public = document.querySelector('#public');

        public.addEventListener('click',() => {
            star.public();
        })
    </script>

11、MVVM.md

mvvm
m:model 数据层

v:view 视图层

vm: 数据双向绑定

在这里插入图片描述

封装mvvm的思路
在这里插入图片描述

1.实现一个Observer,对数据进行劫持,监听数据的属性变更,并在变动时进行notify

Object.defineProperty(vm,key,{
    set(){},
    get(){}
})

2.实现一个compile,对指令进行解析,初始化视图,并且订阅数据的变更,绑定好更新函数

- 解析指令,将指令模板中的变量替换成数据,对视图进行初始化操作
- 订阅数据的变化,绑定好更新函数
- 接收到数据变化,通知视图进行view update

3.实现一个watcher,将其作为以上两者的一个中介点,一方面接收Observer通过Dep传递过来的数据变化,一方面通知compile进行view update

- 通过Dep接收数据变动的通知,实例化的时候将自己添加到dep中
- 数据变更时,接收dep的notify,嗲用自身update方法,触发compile中的绑定的更新函数,进而更新视图

11、threejs–初识webgl和threejs.md

一、webgl介绍

WebGL是浏览器中实现三维效果的一套规范。

WebGL是基于OpenGL ES 2.0的Web标准,可以通过HTML5 Canvas元素作为DOM接口访问

二、什么是threejs?

threejs很简单,你将它理解成three + js就可以了。three表示3D的意思,js表示javascript的意思。合起来threejs就是使用javascript来写3D程序的意思。

参考网站:

官网:

https://threejs.org/

http://www.webgl3d.cn/Three.js/

https://www.ituring.com.cn/book/miniarticle/58552

实例:

商品展示,音乐可视化,数据可视化,…

http://aleksandarrodic.com/p/jellyfish/

三、三大组建

  • 场景(scene)
  • 相机(camera)
  • 渲染器(renderer)

注:三样东西全部具备,才可以使用相机将场景渲染到网页上去。

四、实例
WebGL的渲染是需要html5 Canvas元素的,你可以手动在HTML的中定义Canvas元素,或者让threejs帮你生成。这两种选择一般没有多大差别,我们再次手动再html中定义:

<body onload="init()">
    <canvas id="mainCanvas" width="400px" height="300px" ></canvas>
</body>
  • 渲染器(Renderer)

渲染器和Canvas元素进行绑定,如果之前在HTML中手动定义了id为mainCanvas的Canvas元素,那么Renderer可以这样写:

var renderer = new THREE.WebGLRenderer({
    canvas:document.querySelector('#mainCanvas')
})

如果想要threejs生成canvas元素,在html中就不需要定义canvas元素,在javascript代码中可以这样写:

let renderer = new THREE.WebGLRenderer();

renderer.setSize(400,300);//window.innerWidth  window.innerHeight

document.body.appendChild(renderer.domElement);

通过以下代码可以设置背景色:

renderer.setClearColor(0x000000);
  • 场景(Scene)

在threejs中添加的物体都是添加到场景中的,因此它相当于一个大容器。一般说,场景里没有很复杂的操作,在程序最开始的时候进行实例化,然后将物体添加到场景中即可。

let scene = new THREE.Scene();
  • 照相机(Camera)

在介绍照相机设置前,需要了解一下右手坐标系,如下图:

在这里插入图片描述

//透视摄像机
let camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 1000);
camera.position.set(0, 0, 5);
scene.add(camera);
//注:相机也需要添加到场景中
物体
创建一个x,y,z方向长度分别为100,200,300的长方体,并将其设置为红色。

let box = new THREE.BoxGeometry(100,200,300);
let material = new THREE.MeshBasicMaterial({
    color:0xff0000
})
let cube = new THREE.Mesh(box,material);

scene.add(cube);

最后,渲染
renderer.render(scene,camera);

12、照相机.md

一、什么是照相机?

拍照的机器。咔擦!

我们使用three.js创建的场景是三维的,而通常情况下显示屏是二维的,那么三维场景如何显示到二维的显示屏上呢?照相机就是一个抽象,它定义了三维空间到二维屏幕的投影方式,用“照相机”这样一个类比,可以使我们直观的理解这一投影方式。

针对投影方式的不同:相机分为:正交投影照相机与透视投影照相机

二、正交投影和透视投影的区别:

  • 透视投影

使用透视投影照相机获得的结果是类似人眼再真实世界中看到的有“近大远小”的效果。

参数介绍:

THREE.PerspectiveCamera(fov,aspect,near,far);

修改fov的角度大小:

为什么正方体更小了呢?

从下面的侧视图来看,虽然正方体的实际大小并未改变,但是将照相机的数值张角设置得更大,视景体变大了,因而正方体相对于整个视景体的大小就变小了,看起来正方形就显得变小了。

  • 正交投影

参数介绍:

THREE.OrthographicCamera(left,right,top,bottom,near,far);

这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,我们成为视景体。只有在视景体内部(上图灰色部分)的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。

注:为了保持照相机的横竖比例,需要保证(right-left)/(top-bottom)=canvas.width/canvas.height 一致
实例

在这里插入图片描述

`

13、材质.md

材质

材质(Material)是独立于物体顶点信息之外的与渲染效果相关的属性。 通过设置材质可以改变物体的颜色、纹理、贴图、光照模式等。

  • 基本材质:THREE.MeshBasicMaterial(opt)

使用基本材质的物体,渲染后物体的颜色始终为该材质的颜色,而不会由于光照产生明暗、阴影效果。

   new THREE.MeshBasicMaterial({
        color: 0xffff00,
        opacity: 0.75
    });
  • Lambert材质 :

lambert材质是符合lambert光照模型的材质。lambert光照模型的主要特点是只考虑漫反射不考虑镜面反射的效果,因而对金属、镜子等需要反射的物体就不适应,对于其他大部分物体的漫反射效果都是适用的。

  new THREE.MeshLambertMaterial({
        color: 0xffff00
    })
  • Phong材质:

Phong材质是符合Phong光照模型的材质。和Lambert不同的是,Phong模型考虑镜面反射的效果,因此对金属。镜面的表现尤为适合。

   new THREE.MeshPhongMaterial({
        color: 0xffff00
    });

在这里插入图片描述

  • 法向材质
new THREE.MeshNormalMaterial()

在这里插入图片描述

材质的纹理贴图

let texture = new THREE.TextureLoader().load('图片的路径',() => {
    //再次渲染
})

综上所述:
在这里插入图片描述

14.1-typescript介绍和环境安装.md

一、typescript是微软推出的一门语言,它是javascript的超集,包含es.
在这里插入图片描述

二、typescript的运行环境
在这里插入图片描述

- ts运行环境的安装与运行

全局安装: typescript npm i typescript -g

检查是否安装:tsc -v

tsc的作用: 负责将ts代码转为浏览器和nodejs可以识别的js代码。

执行: tsc 文件路径 --watch 监听

  • 在vscode里配置自动将ts文件编译为js文件

1.运行tsc --init,创建tsconfig.json文件

2.在tsconfig.json文件,设置js文件:‘outDir’:’./js/’

3.设置vscode监视任务,之后修改项目中的ts,自动生成js文件。

注:如果在vscode不能运行,可以执行 tsc ./index.ts --outDir ./js/(输出到的文件夹) --watch(监听)

14.2-typescript变量与数据类型.md

一、数据类型

  • 原有的类型:string number boolean null undefined symbol array object
  • 新增类型:tuple元组 enum枚举 any 任意类型 never void

定义变量

  • 基本类型定义:string number boolean null undefined symbol any
  • 基本类型变量定义:let 变量名:变量类型 = 值;
let name:string = 'lili';

name=12; //会报错

let num:number = 1;

let isLogin:boolean = true;

let aa:null = null;

let b:undefined = undefined;

定义一个symbol类型的变量,会报错

定义一个symbol类型的值。

let s1:symbol = Symbol(1);  //会报错

可以修改tsconfig.json,修改如下:

{
    "compilerOptions": {
        "target": "es6",
        "lib":["dom","es2015"]
    }
}

数组的定义:

  • 方式一:let 数组名:类型[] = [值1,值2];
let nameList:string[] = ['zs','lisi','lili'];
  • 方式二:let 数组名:Array<类型>
let numArr:Array<number>= [1,2,3];

定义一个数组内元素类型不确定的数组

let arr1:any[] = ['hello',true];

元组:就是一个规定了元素数量和每个元素类型的“数组”,而每个元素的类型,可以不相同。
元组的定义:let 元组名:[类型1,类型2,类型3] = [值1,值2,值3]

let tup1:[string,number,boolean] = ['你好',19,true];

枚举:
枚举定义:

enum 枚举名{
    枚举项1=枚举值1,
    枚举项2=枚举值2
    ...
}


enum Sex{
    girl=1,
    boy=2,
    unkown=3
}

使用默认枚举值:

enum Sex{
    girl,    ---->  0
    boy,     ---->  1
    unkown   ---->  2
}

let sex1 = Sex.girl; --->  0

any任意类型

let str:any = 123;

str = 'lili';

let arr:any[] = ['张三',true,12];

arr[3] = '12';

void和any正好相反,它表示没有任何类型

当一个函数没有任何返回值时,可以设置void类型

let str:void=10;//报错

let str:void = null;//报错

let str:void = undefined //不报错

never:表示那些永远不存在的值的类型

使用场景:1.抛出异常的函数

function errorFun(msg:string):never{
throw new Error(msg)
}

errorFun(msg:‘发生未知错误’);

//2.死循环

function notEnd():never{
while(true){

}

}

类型推断:

let str1 = 'hello';

str1 = 1;

注:如果在给声明str1的时候没有给类型,会被自动推断数据类型,后续在修改其它类型就会报错。
联合类型

let param:string|number = 1;

param = 'a';
类型断言

有时候你会遇到这样的情况,你会比ts更了解某个值的详细信息。 通常这会发生再你清除地知道一个实体具有比它现有类型更确切的类型。
  • 第一种:"尖括号"语法
let someValue:any = 'this is a string';

let strLength:number = (<string>someValue).length;

console.log(strLength,'strLength')
  • 第二种:as语法
let someValue1:any = "this is astring";

let strLength2:number = (someValue1 as string).length;

console.log(strLength2,"strLength2")

14.3-typescript–函数.md

函数的定义

需要指定函数参数和返回值的类型

    function fullName(firstName:string,lastName:string):string{
        return firstName+lastName
    }
    

如果没有返回值时,需要给void类型

    function login(username:string,password:string):void{
        //调用登录的接口
    }
    

可选参数,尽量放在最后

    //第一种:
    function getList(pagenum:number=1,limit?:number):void{
        //调用获取列表的接口
    }
    
    //第二种:
    function getList(pagenum:number=1,limit:number=10):void{
        //调用获取列表的接口
    }
    

函数剩余参数

function sum(...nums:number[]):number{
    return nums.reduce((pre,cur)=>{
        return pre+cur
    })
}

函数重载

函数重载在同一范围内声明几个功能类似的同名函数,但是这些同名函数的形参(参数的个数,类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。

function getInfo(name:string):string;
function getInfo(age:number):string;
function getInfo(param:any):any{
    if(typeof param === 'string'){
        return `我叫:${param}`
    }else if(typeof param == 'number'){
        return `我${param}岁`
    }else{
        return '输入错误'
    }
}

console.log(getInfo('lili'))

console.log(getInfo(18)

console.log(getInfo(true) //报错

14.4-typescript-class类.md

class类

class Person{
    name:string;
    age:number;
    constructor(n:string,a:number){
        this.name = n;
        this.age = a;
    }
    
    sayName():string{
        return `我叫${this.name},今年${this.age}`
    }
    
    setName(name:string,age:number=20):void{
        this.name = name;
        this.age = age;
    }
}

let p = new Person('lili',19);

class Student extends Person{
    constructor(name:string,age:number){
        super(name,age);
    }
}

访问控制修饰符

  • public: (默认) 共有,可以在任何地方被访问
  • protected:受保护,可以被其自身以及子类和父类访问
  • private:私有,只能被其定义所在的类访问

14.5-typescript-接口.md

接口

接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到了一种限制和规范的作用。

接口的首字母一般都大写

定一个数组对象

interface Product{
    id:number,
    name:string,
    price:number,
    desc:string
}

let productList:Product[] = [
    {id:1,name:'香蕉',price:10,desc:'好吃'},
    {id:2,name:'苹果',price:4,desc:'好吃'},
    {id:3,name:'桔子',price:3,desc:'好吃'}
];

定义一个对象

 interface Shape{
        color:string,
        width:number,
        height:number
    }
    
    let square:Shape = {color:'red',width:80,height:80};

属性接口 对json的约束

interface Info{
    firstName:string,
    lastName:string,
    age?:number, //可选属性
    [propName:string]:any //配置任意属性  键必须是字符串类型:值为任意类型
}

function printName(info:Info):void{
    if(info.age){
        console.log(`我叫:${info.firstName},${info.lastName},年龄:${info.age}`)
    }else{
        console.log(`我叫:${info.firstName},${info.lastName},年龄:保密`)
    }  
}

printName({firstName:'lili',lastName:'zhang',age:18,address:'石家庄'})

函数的类型接口:对函数传入的参数和返回值进行约束–批量约束

interface CompareInterface{
    (first:number,last:number):boolean
}

// function compareFun(first:number,last:number):boolean{
//     return first > last
// }

// let compareFun:CompareInterface =function(first:number,last:number):boolean{
//     return first > last
// }

//简写
// let compareFun:CompareInterface =function(f:number,l:number):boolean{
//     return f > l
// }

//最终简写成

let compareFun:CompareInterface = function(f,l){
    return f > l
}

compareFun(2,1)

可索引类型

interface Arr{
    [index:number]:string
}

let arr:Arr = ['str1','str2'];

console.log(arr[1]);

//对象的
interface Obj {
    [index:string]:any
}

let obj:Obj = {name:'lili',age:20};

console.log(obj.name);
console.log(obj.age);

类类型接口

interface Animal{
    name:string;
    eat(str:string):void
}

class Dog implements Animal{
    name:string;
    constructor(name:string){
        this.name = name;
    }
    eat(str:string):void{
        console.log(this.name+'吃'+str)
    }
}

let d = new Dog('达摩');
d.eat('肉');

class Cat implements Animal{
    name:string;
    constructor(name:string){
        this.name = name;
    }
    eat(str:string):void{
        console.log(this.name+'吃'+str)
    }
}

let cat = new Cat('小花');
cat.eat();

接口继承接口

interface Shape{
    color:string
}

interface PenWidth{
    width:number
}

interface Square extends Shape,PenWidth{
    height:number
}

let square:Square = {color:'red',width:80,height:80};

14.6-typescript-泛型.md

泛型

泛型是指在定义函数,接口或类的时候,不预先预定具体的类型,而在使用的时候再指定类型的一种特性。

泛型函数

案例一:

function getData<T>(value:T):T{
    return value
}

//给类型默认值
function getData1<T=string>(value:T):T{
    return value
}

console.log(getData<number>(123));
console.log(getData<string>('hello');

案例二:

function getMin<T>(arr:T[]):T{
    let minNum = arr[0];
    for(let i = 0;i<arr.length;i++){
        if(minNum > arr[i]){
            minNum = arr[i];
        }
    }
    return minNum
}

console.log(getMin<string>(['a','d','p']))
console.log(getMin<number>([11,4,6]))

案例三

function createArray<T>(length:number,value:T):T[]{
    let arr:T[] = [];

    for(let i = 0;i<length;i++){
        arr.push(value);
    }

    return arr
}

console.log(createArray<string>(3,'x'))
console.log(createArray(3,'x'))

案例四  多个类型参数

function swap<T,U>(tuple:[T,U]):[U,T]{
    return [tuple[1],tuple[0]]
}

console.log(swap([5,'hello']))

泛型类

class MinClass<T>{
    public list:T[]=[];
    add(num:T){
        this.list.push(num);
    }
    min():T{
        let minNum = this.list[0];
        for(let i = 0;i<this.list.length;i++){
            if(minNum > this.list[i]){
                minNum = this.list[i];
            }
        }

        return minNum
    }
}

let m = new MinClass<number>();

m.add(5);
m.add(100);
m.add(30);

console.log(m.min());

let str = new MinClass<string>();

str.add('o');

str.add('c');

str.add('b');

console.log(str.min())

泛型接口

类添加泛型接口

interface IData<T> {
    Add(info: T): boolean;
    Delete(info: T): boolean;
    Update(info: T): T;
    Search(info: T, id: number): T;
}
 
// 对mysql访问
class MysqlData<T> implements IData<T>{
    Add(info: T): boolean {
        return false;
    }
 
    Delete(info: T): boolean {
        return false;
    }
 
    Search(info: T, id: number): T {
        // @ts-ignore
        return info;
    }
 
    Update(info: T): T {
        // @ts-ignore
        return undefined;
    }
 
}

new MysqlData<string>()

14.7-typescript装饰器.md

装饰器是一种特殊类型的声明,本质上就是一个方法,可以注入到类,方法,属性,参数上,扩展其功能。

常见的装饰器:类装饰器 属性装饰器 方法装饰器 参数装饰器

装饰器在写法:普通装饰器(无法传参)、装饰器工厂(可传参)

  • 类装饰器
类装饰器

function logClz(params:any){
    console.log(params);
    params.prototype.url = 'http://localhost:8080';
    params.prototype.run = function(){
        console.log('run-------')
    }
}

@logClz
class HttpClient{
    constructor(){}
}

let http:any = new HttpClient();

http.run();
  • 装饰器工厂:闭包
function logClz(params:string){
    console.log('params:',params);
    return function(target:any){
        console.log('target:',target);
        target.prototype.url = params;
    }
}

@logClz('hello')
class HttpClient{
    constructor(){}
}

let http:any = new HttpClient();

console.log(http.url);
  • 重载构造函数
function logClz(target:any){
    return class extends target{
        url = 'change url';
        getData(){
            console.log('getData',this.url);
        }
    }
}

@logClz
class HttpClient{
    public url:string|undefined;
    constructor(){
        this.url = 'init url'
    }

    getData(){
        console.log(this.url);
    }
}

let http = new HttpClient();

http.getData();
 constructor(){}
}

let http:any = new HttpClient();

http.run();
  • 装饰器工厂:闭包
function logClz(params:string){
    console.log('params:',params);
    return function(target:any){
        console.log('target:',target);
        target.prototype.url = params;
    }
}

@logClz('hello')
class HttpClient{
    constructor(){}
}

let http:any = new HttpClient();

console.log(http.url);
  • 重载构造函数
function logClz(target:any){
    return class extends target{
        url = 'change url';
        getData(){
            console.log('getData',this.url);
        }
    }
}

@logClz
class HttpClient{
    public url:string|undefined;
    constructor(){
        this.url = 'init url'
    }

    getData(){
        console.log(this.url);
    }
}

let http = new HttpClient();

http.getData();
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值