首页

Three.js 基础入门

前端达人

课程介绍

近些年,浏览器的功能越来越强大,渐渐得成为了复杂应用和图形的平台。同时,现有大多数浏览器实现了对 WebGL 的支持,但要直接使用 WebGL 相关接口进行开发,则需要学习复杂的着色器语言,且开发周期长,不利于项目的快速开发。

面对这种情况,Three.js 应运而生,它不但对 WebGL 进行了封装,将复杂的接口简单化,而且基于面向对象思维,将数据结构对象化,非常方便我们开发。Three.js 的发展十分迅速,然而配套的学习材料却比较匮乏,于是便有了当前的这个课程。

本课程作为入门课程,不会深入做源码解析,主要协助初学者了解 Three.js 的数据结构,基础 API 以及相关辅助插件的使用。帮助初学者达到快速入门的目的。

本课程共包含四大部分。

第一部分(第01-02课),入门前概述,带你初步认识 Three.js、框架选择标准、开发工具,源码获取,实现一个“Hello World”辅助工具。
第二部分(第03-08课),基础功能篇,主要包括 Object3D、Scene、Mesh、Group、Geometry、Materials、Lights、Cameras、粒子等相关功能的介绍。
第三部分(第09-15课),进阶篇,主要包括 Controls、Loaders、Animation、Tween、核心对象,与场景之间的交互以及性能优化介绍。
第四部分(第16课),实战篇,带大家利用所学知识实现一个 3D 小案例。

作者简介

郑世强,现就职于上海某网络公司担任前端工程师,CSDN 博客作者,长期活跃于各大论坛,擅长前端开发、WEBGL 开发。

课程内容

第01课:入门前准备

什么是 WebGL?

WebGL(Web 图形库)是一种 JavaScript API,用于在任何兼容的 Web 浏览器中呈现交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 紧密相符合的 API,可以在 HTML5 <canvas> 元素中使用(简介引自 MDN)。

以我的理解,WebGL 给我们提供了一系列的图形接口,能够让我们通过 JavaScript 去使用 GPU 来进行浏览器图形渲染的工具。


什么是 Three.js?

Three.js 是一款 webGL 框架,由于其易用性被广泛应用。Three.js 在 WebGL 的 API 接口基础上,又进行的一层封装。它是由居住在西班牙巴塞罗那的程序员 Ricardo Cabbello Miguel 所开发,他更为人知的网名是 Mr.doob。



Three.js 以简单、直观的方式封装了 3D 图形编程中常用的对象。Three.js 在开发中使用了很多图形引擎的高级技巧,极大地提高了性能。另外,由于内置了很多常用对象和极易上手的工具,Three.js 的功能也非常强大。最后,Three.js 还是完全开源的,你可以在 GitHub 上找到它的源代码,并且有很多人贡献代码,帮助 Mr.doob 一起维护这个框架。

WEBGL 和 Three.js 的关系

WebGL 原生 API 是一种非常低级的接口,而且还需要一些数学和图形学的相关技术。对于没有相关基础的人来说,入门真的很难,Three.js 将入门的门槛降低了一大截,对 WebGL 进行封装,简化我们创建三维动画场景的过程。只要你有一定的 JavaScript 基础,有一定的前端经验,我坚信,用不了多长时间,三维制作会变得很简单。



用最简单的一句话概括:WebGL 和 Three.js 的关系,相当于 JavaScript 和 jQuery 的关系。

功能概述

Three.js 作为 WebGL 框架中的佼佼者,由于它的易用性和扩展性,使得它能够满足大部分的开发需求,Three.js 的具体功能如下:


Three.js 掩盖了 3D 渲染的细节:Three.js 将 WebGL 原生 API 的细节抽象化,将 3D 场景拆解为网格、材质和光源(即它内置了图形编程常用的一些对象种类)。
面向对象:开发者可以使用上层的 JavaScript 对象,而不是仅仅调用 JavaScript 函数。
功能非常丰富:Three.js 除封装了 WebGL 原始 API 之外,Three.js 还包含了许多实用的内置对象,可以方便地应用于游戏开发、动画制作、幻灯片制作、髙分辨率模型和一些特殊的视觉效果制作。
速度很快:Three.js 采用了 3D 图形最佳实践来保证在不失可用性的前提下,保持极高的性能。
支持交互:WebGL 本身并不提供拾取(Picking)功能(即是否知道鼠标正处于某个物体上)。而 Three.js 则固化了拾取支持,这就使得你可以轻松为你的应用添加交互功能。
包含数学库:Three.js 拥有一个强大易用的数学库,你可以在其中进行矩阵、投影和矢量运算。
内置文件格式支持:你可以使用流行的 3D 建模软件导出文本格式的文件,然后使用 Three.js 加载,也可以使用 Three.js 自己的 JSON 格式或二进制格式。
扩展性很强:为 Three.js 添加新的特性或进行自定义优化是很容易的事情。如果你需要某个特殊的数据结构,那么只需要封装到 Three.js 即可。
支持HTML5 Canvas:Three.js 不但支持 WebGL,而且还支持使用 Canvas2D、Css3D 和 SVG 进行渲染。在未兼容 WebGL 的环境中可以回退到其它的解决方案。


缺点

虽然 Three.js 的优势很大,但是它也有它的不足之处:



官网文档非常粗糙,对于新手极度不友好。

国内的相关资源匮乏。

Three.js 所有的资料都是以英文格式存在,对国内的朋友来说又提高了门槛。

Three.js 不是游戏引擎,一些游戏相关的功能没有封装在里面,如果需要相关的功能需要进行二次开发。


Three.js 与其他库的对比

随着 WebGL 的迅速发展,相关的 WebGL 库也丰富起来,接下来介绍几个比较火的 WebGL 库。



与 Babylon.js 对比

Babylon.JS 是最好的 JavaScript 3D 游戏引擎,它能创建专业级三维游戏。主要以游戏开发和易用性为主。与 Three.js 之间的对比:



Three.js 比较全面,而 Babylon.js 专注于游戏方面。

Babylon.js 提供了对碰撞检测、场景重力、面向游戏的照相机,Three.js 本身不自带,需要依靠引入插件实现。

对于 WebGL 的封装,双方做得各有千秋,Three.js 浅一些,好处是易于扩展,易于向更底层学习;Babylon.js 深一些,好处是易用扩展难度大一些。

Three.js 的发展依靠社区推动,出来的比较早,发展比较成熟,Babylon.js 由微软公司在2013推出,文档和社区都比较健全,国内还不怎么火。


与 PlayCanvas 对比

PlayCanvas 是一个基于 WebGL 游戏引擎的企业级开源 JavaScript 框架,它有许多的开发工具能帮你快速创建 3D 游戏。与 Three.js 之间的对比:



PlayCanvas 的优势在于它有云端的在线可视化编辑工具。

PlayCanvas 的扩展性不如 Three.js。

最主要是 PlayCanvas 不完全开源,还商业付费。


与 Cesium 对比

Cesium 是国外一个基于 JavaScript 编写的使用 WebGL 的地图引擎,支持 3D、2D、2.5D 形式的地图展示,可以自行绘制图形,高亮区域。与 Three.js 对比:



Cesium 是一个地图引擎,专注于 Gis,相关项目推荐使用它,其它项目还是算了。

至于库的扩展,其它的配套插件,以及周边的资源都不及Three.js。


总结

通过以上信息我们发现,Three.js 在其库的扩展性,易用性以及功能方面有很好的优势。学习 Three.js 入门 3D 开发不但门槛低,而且学习曲线不会太陡,即使以后转向 WebGL 原生开发,也能通过 Three.js 学习到很多有用的知识。



现在最火的微信小游戏跳一跳也是在 Three.js 的基础上开发出来的。所以,Three.js 是我们必须要学的 WebGL 框架。


入门前准备

浏览器兼容


Three.js 可以使用 WebGL 在所有现代浏览器上渲染场景。对于旧版浏览器,尤其是 Internet Explorer 10 及更低版本,您可能需要回退到其他渲染器(CSS2DRenderer、CSS3DRenderer、SVGRenderer、CanvasRenderer)。



注意:如果您不需要支持这些旧版浏览器,则不推荐使用其他渲染器,因为它们速度较慢并且支持的功能比 WebGLRenderer 更少。


点击查看原图

即可下载当前版本的代码及相关案例,文件下载解压后是这样的:


微信截图_20200529213036.png

其中相关文件夹的内容是:

build:里面含有 Three.js 构建出来的 JavaScript 文件,可以直接引入使用,并有压缩版;
docs:Three.js 的官方文档;
editor:Three.js 的一个网页版的模型编辑器;
examples:Three.js 的官方案例,如果全都学会,必将成为大神;
src:这里面放置的全是编译 Three.js 的源文件;
test:一些官方测试代码,我们一般用不到;
utils:一些相关插件;
其他:开发环境搭建、开发所需要的文件,如果不对 Three.js 进行二次开发,用不到。
还有第三种,就是直接去 GitHub 上下载源码,和在官网上下载的代码一样。

hello World

前面说了这么多,准备了这么多,最后,放上我们的第一个案例吧。由此来打开学习 Three.js 的大门:


<!DOCTYPE html><html><head>    <meta charset=utf-8>    <title>我的第一个Three.js案例</title>    <style>        body {            margin: 0;        }        canvas {            width: 100%;            height: 100%;            display: block;        }    </style></head><body onload="init()"><script src="https://cdn.bootcss.com/three.js/92/three.js"></script><script>    //声明一些全局变量    var renderer, camera, scene, geometry, material, mesh;    //初始化渲染器    function initRenderer() {        renderer = new THREE.WebGLRenderer(); //实例化渲染器        renderer.setSize(window.innerWidth, window.innerHeight); //设置宽和高        document.body.appendChild(renderer.domElement); //添加到dom    }    //初始化场景    function initScene() {        scene = new THREE.Scene(); //实例化场景    }    //初始化相机    function initCamera() {        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200); //实例化相机        camera.position.set(0, 0, 15);    }    //创建模型    function initMesh() {        geometry = new THREE.BoxGeometry( 2, 2, 2 ); //创建几何体        material = new THREE.MeshNormalMaterial(); //创建材质        mesh = new THREE.Mesh( geometry, material ); //创建网格        scene.add( mesh ); //将网格添加到场景    }    //运行动画    function animate() {        requestAnimationFrame(animate); //循环调用函数        mesh.rotation.x += 0.01; //每帧网格模型的沿x轴旋转0.01弧度        mesh.rotation.y += 0.02; //每帧网格模型的沿y轴旋转0.02弧度        renderer.render( scene, camera ); //渲染界面    }    //初始化函数,页面加载完成是调用    function init() {        initRenderer();        initScene();        initCamera();        initMesh();        animate();    }</script></body></html>

请将上面的代码复制到 HTML 文件中,然后使用浏览器打开,我们就会发现下面的效果:

点击查看原图



————————————————

版权声明:本文为CSDN博主「GitChat的博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/valada/java/article/details/80871701







JavaScript的padStart()和padEnd()格式化字符串使用技巧

seo达人

用例

让我们从介绍几种不同的填充用例开始。


标签和值

假设你在同一行上有标签和值,例如 name:zhangsan 和 Phone Number:(555)-555-1234。如果把他们放在一起看起来会有点奇怪,会是这样:


Name: zhangsan

Phone Number: (555)-555-1234

你可能想要这个。


Name:           zhangsan

Phone Number:   (555)555-1234

或这个...


       Name: zhangsan

Phone Number: (555)555-1234

金额

在中国,显示价格时通常显示两位数的角、分。所以代替这个...


¥10.1

你会想要这个。


¥10.01

日期

对于日期,日期和月份都需要2位数字。所以代替这个...


2020-5-4

你会想要这个。


2020-05-04

时间

与上面的日期类似,对于计时器,你需要2位数字表示秒,3位数字表示毫秒。所以代替这个...


1:1

你会想要这个。


01:001

padstart()

让我们从 padStart() 以及标签和值示例开始。假设我们希望标签彼此正确对齐,以使值在同一位置开始。


       Name: zhangsan

Phone Number: (555)555-1234

由于 Phone Number 是两个标签中较长的一个,因此我们要在 Name 标签的开头加上空格。为了将来的需要,我们不要把它专门填充到电话号码的长度,我们把它填充到长一点,比如说20个字符。这样一来,如果你在未来使用较长的标签,这一招仍然有效。


在填充之前,这是用于显示此信息的入门代码。


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log(label1 + ": " + name);

console.log(label2 + ": " + phoneNumber);


//Name: zhangsan

//Phone Number: (555)-555-1234

现在,让我们填充第一个标签。要调用 padStart(),你需要传递两个参数:一个用于填充字符串的目标长度,另一个用于你希望填充的字符。在这种情况下,我们希望长度为20,而填充字符为空格。


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log(label1.padStart(20, " ") + ": " + name);

console.log(label2 + ": " + phoneNumber);


//               Name: zhangsan

////Phone Number: (555)-555-1234

现在填充第二行。


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log(label1.padStart(20, " ") + ": " + name);

console.log(label2.padStart(20, " ") + ": " + phoneNumber);


//               Name: zhangsan

////     Phone Number: (555)-555-1234

padEnd()

对于相同的标签和值示例,让我们更改填充标签的方式。让我们将标签向左对齐,以便在末尾添加填充。


初始代码


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log(label1 + ": " + name);

console.log(label2 + ": " + phoneNumber);


//Name: zhangsan

//Phone Number: (555)-555-1234

现在,让我们填充第一个标签,与我们之前所做的类似,但有两个小区别。现在,我们使用 padEnd() 而不是padStart(),并且需要在填充之前将冒号与标签连接起来,这样我们就能确保冒号在正确的位置。


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log((label1 + ': ').padEnd(20, ' ') + name);

console.log(label2 + ": " + phoneNumber);


//Name:               zhangsan

//Phone Number: (555)-555-1234

现在两行都已填充。


const label1 = "Name";

const label2 = "Phone Number";

const name = "zhangsan"

const phoneNumber = "(555)-555-1234";


console.log((label1 + ': ').padEnd(20, ' ') + name);

console.log((label2 + ': ').padEnd(20, ' ') + phoneNumber);


//Name:               zhangsan

//Phone Number:       (555)-555-1234

数字(价格、日期、计时器等)呢?

padding函数是专门针对字符串而不是数字的,所以,我们需要先将数字转换为字符串。


价格

让我们看一下显示价格的初始代码。


const rmb = 10;

const cents = 1;

console.log("¥" + rmb + "." + cents); //¥10.1

要填充分,我们需要先将其转换为字符串,然后调用 padStart() 函数,指定长度为1且填充字符为'0';


const rmb = 10;

const cents = 1;

console.log("¥" + rmb + "." + cents.toString().padStart(2,0)); //¥10.01

日期

这是显示日期的初始代码。


const month = 2;

const year = 2020;


console.log(year + "-" + month); //2020-2

现在,让我们填充月份以确保它是两位数。


const month = 2;

const year = 2020;


console.log(year + "-" + month.toString().padStart(2,"0")); // 2020-02

计时器

最后是我们的计时器,我们要格式化两个不同的数字,即秒和毫秒。尽管有相同的原则。这是初始代码。


const seconds = 1;

const ms = 1;


console.log(seconds + ":" + ms); //1:1

现在要填充,我将在单独的行上进行填充,以便于阅读。


const seconds = 1;

const formattedSeconds = seconds.toString().padStart(2,0);

const ms = 1;

const formattedMs = ms.toString().padStart(3,0);


console.log(formattedSeconds + ":" + formattedMs); // 01:001

最后

虽然编写自己的padding函数并不难,但既然已经内置在JavaScript中,为什么还要自己去做呢?有很多有趣的函数已经内置了。在你自己构建一些东西之前,可能值得先快速搜索一下。

JavaScript版数据结构与算法——基础篇(一)

前端达人

数组

数组——最简单的内存数据结构

数组存储一系列同一种数据类型的值。( Javascript 中不存在这种限制)

对数据的随机访问,数组是更好的选择,否则几乎可以完全用 「链表」 来代替

在很多编程语言中,数组的长度是固定的,当数组被填满时,再要加入新元素就很困难。Javascript 中数组不存在这个问题。

但是 Javascript 中的数组被实现成了对象,与其他语言相比,效率低下。

数组的一些核心方法

方法 描述
push 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。(改变原数组)
pop 方法从数组中删除最后一个元素,并返回该元素的值。(改变原数组)
shift 方法从数组中删除第一个元素,并返回该元素的值,如果数组为空则返回 undefined 。(改变原数组)
unshift 将一个或多个元素添加到数组的开头,并返回该数组的新长度(改变原数组)
concat 连接两个或多个数组,并返回结果(返回一个新数组,不影响原有的数组。)
every 对数组中的每个元素运行给定函数,如果该函数对每个元素都返回 true,则返回 true。若为一个空数组,,始终返回 true。 (不会改变原数组,[].every(callback)始终返回 true)
some 对数组中的每个元素运行给定函数,如果任一元素返回 true,则返回 true。若为一个空数组,,始终返回 false。(不会改变原数组,)
forEach 对数组中的每个元素运行给定函数。这个方法没有返回值,没有办法中止或者跳出 forEach() 循环,除了抛出一个异常(foreach不直接改变原数组,但原数组可能会被 callback 函数该改变。)
map 对数组中的每个元素运行给定函数,返回每次函数调用的结果组成的数组(map不直接改变原数组,但原数组可能会被 callback 函数该改变。)
sort 按照Unicode位点对数组排序,支持传入指定排序方法的函数作为参数(改变原数组)
reverse 方法将数组中元素的位置颠倒,并返回该数组(改变原数组)
join 将所有的数组元素连接成一个字符串
indexOf 返回第一个与给定参数相等的数组元素的索引,没有找到则返回 -1
lastIndexOf 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值,没有找到则返回 -1
slice 传入索引值,将数组里对应索引范围内的元素(浅复制原数组中的元素)作为新数组返回(原始数组不会被改变)
splice 删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容(改变原数组)
toString 将数组作为字符串返回
valueOf 和 toString 类似,将数组作为字符串返回

是一种遵循后进先出(LIFO)原则的有序集合,新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

通俗来讲,就是你向一个桶里放书本或者盘子,你要想取出最下面的书或者盘子,你必须要先把上面的都先取出来。

栈也被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录 (浏览器的返回按钮)。

代码实现

// 封装栈
    function Stack() {
        // 栈的属性
        this.items = []

        // 栈的操作
        // 1.将元素压入栈
        Stack.prototype.push = function (element) {
            this.items.push(element)
        }
        // 2.从栈中取出元素
        Stack.prototype.pop = function () {
            return this.items.pop()
        }
        // 3.查看栈顶元素
        Stack.prototype.peek = function () {
            return this.items[this.items.length - 1]
        }
        // 4.判断是否为空
        Stack.prototype.isEmpty = function () {
            return this.items.length === 0
        }
        // 5.获取栈中元素的个数
        Stack.prototype.size = function () {
            return this.items.length
        }
        // 6.toString()方法
        Stack.prototype.toString = function () {
            let str = ''
            for (let i = 0; i< this.items.length; i++) {
                str += this.items[i] + ' '
            }
            return str
        }

    }

    // 栈的使用
    let s = new Stack()

队列

队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新
元素,并从顶部移除元素。添加的元素必须排在队列的末尾。

生活中常见的就是排队

代码实现

function Queue() {
        this.items = []
        // 1.将元素加入队列
        Queue.prototype.enqueue = function (element) {
            this.items.push(element)
        }
        // 2.从队列前端删除元素
        Queue.prototype.dequeue = function () {
            return this.items.shift()
        }
        // 3.查看队列前端元素
        Queue.prototype.front = function () {
            return this.items[0]
        }
        // 4.判断是否为空
        Queue.prototype.isEmpty = function () {
            return this.items.length === 0
        }
        // 5.获取队列中元素的个数
        Queue.prototype.size = function () {
            return this.items.length
        }
        // 6.toString()方法
        Queue.prototype.toString = function () {
            let str = ''
            for (let i = 0; i< this.items.length; i++) {
                str += this.items[i] + ' '
            }
            return str
        }
    }
    
    // 队列使用
    let Q = new Queue()

优先级队列:

代码实现


function PriorityQueue() {
        function QueueElement(element, priority) {
            this.element = element
            this.priority = priority
        }
        this.items = []

        PriorityQueue.prototype.enqueue = function (element, priority) {
            let queueElement = new QueueElement(element,priority)

            // 判断队列是否为空
            if (this.isEmpty()) {
                this.items.push(queueElement)
            } else {
                let added = false // 如果在队列已有的元素中找到满足条件的,则设为true,否则为false,直接插入队列尾部
                for (let i = 0; i< this.items.length; i++) {
                    // 假设priority值越小,优先级越高,排序越靠前
                    if (queueElement.priority < this.items[i].priority) {
                        this.items.splice(i, 0, queueElement)
                        added = true
                        break
                    }
                }
                if (!added) {
                    this.items.push(queueElement)
                }
            }

        }
        
    }
    

链表

链表——存储有序的元素集合,但在内存中不是连续放置的。


链表(单向链表)中的元素由存放元素本身「data」 的节点和一个指向下一个「next」 元素的指针组成。牢记这个特点

相比数组,链表添加或者移除元素不需要移动其他元素,但是需要使用指针。访问元素每次都需要从表头开始查找。

代码实现:
单向链表


function LinkedList() {
        function Node(data) {
            this.data = data
            this.next = null

        }
        this.head = null // 表头
        this.length = 0
        // 插入链表
        LinkedList.prototype.append = function (data) {
            // 判断是否是添加的第一个节点
            let newNode = new Node(data)
            if (this.length == 0) {
                this.head = newNode
            } else {
                let current = this.head
                while (current.next) { 
                // 如果next存在,
                // 则当前节点不是链表最后一个
                // 所以继续向后查找
                    current = current.next
                }
                // 如果next不存在
                 // 则当前节点是链表最后一个
                // 所以让next指向新节点即可
                current.next = newNode
            }
            this.length++
        }
        // toString方法
        LinkedList.prototype.toString = function () {
            let current = this.head
            let listString = ''
            while (current) {
                listString += current.data + ' '
                current = current.next
            }
            return listString
        }
         // insert 方法
        LinkedList.prototype.insert = function (position, data) {
            if (position < 0 || position > this.length) return false
            let newNode = new Node(data)
            if (position == 0) {
                newNode.next = this.head
                this.head = newNode
            } else {
                let index = 0
                let current = this.head
                let prev = null
                while (index++ < position) {
                    prev = current
                    current = current.next
                }
                newNode.next = current
                prev.next = newNode
            }
            this.length++
            return true
        }
        // get方法
        LinkedList.prototype.get = function (position) {
            if (position < 0 || position >= this.length) return null
            let index = 0
            let current = this.head
            while (index++ < position){
                current = current.next
            }
            return current.data
        }
        LinkedList.prototype.indexOf = function (data) {
            let index = 0
            let current = this.head
            while (current) {
                if (current.data == data) {
                    return index
                } else {
                    current = current.next
                    index++
                }
            }

            return  -1
        }
        LinkedList.prototype.update = function (position, data) {
            if (position < 0 || position >= this.length) return false
            let index = 0
            let current = this.head
            while (index++ < position) {
                current = current.next
            }
            current.data = data
            return  true
        }
        LinkedList.prototype.removeAt = function (position) {
            if (position < 0 || position >= this.length) return null
            if (position == 0) {
                this.head = this.head.next
            } else {
                let index = 0
                let current = this.head
                let prev = null
                while (index++ < position) {
                    prev = current
                    current = current.next
                }
                prev.next = current.next
            }
            this.length--
            return  true


        }
        LinkedList.prototype.remove = function (data) {
            let postions = this.indexOf(data)

            return this.removeAt(postions)
        }
        
    }

    let list = new LinkedList()
双向链表:包含表头表尾 和 存储数据的 节点,其中节点包含三部分:一个链向下一个元素的next, 另一个链向前一个元素的prev 和存储数据的 data牢记这个特点

function doublyLinkedList() {
        this.head = null // 表头:始终指向第一个节点,默认为 null
        this.tail = null // 表尾:始终指向最后一个节点,默认为 null
        this.length = 0 // 链表长度

        function Node(data) {
            this.data = data
            this.prev = null
            this.next = null
        }

        doublyLinkedList.prototype.append = function (data) {
            let newNode = new Node(data)

            if (this.length === 0) {
            // 当插入的节点为链表的第一个节点时
            // 表头和表尾都指向这个节点
                this.head = newNode
                this.tail = newNode
            } else {
            // 当链表中已经有节点存在时
            // 注意tail指向的始终是最后一个节点
            // 注意head指向的始终是第一个节点
            // 因为是双向链表,可以从头部插入新节点,也可以从尾部插入
            // 这里以从尾部插入为例,将新节点插入到链表最后
            // 首先将新节点的 prev 指向上一个节点,即之前tail指向的位置
                newNode.prev = this.tail
            // 然后前一个节点的next(及之前tail指向的节点)指向新的节点
            // 此时新的节点变成了链表的最后一个节点
                this.tail.next = newNode
            // 因为 tail 始终指向的是最后一个节点,所以最后修改tail的指向
                this.tail = newNode
            }
            this.length++
        }
        doublyLinkedList.prototype.toString = function () {
            return this.backwardString()
        }
        doublyLinkedList.prototype.forwardString = function () {
            let current = this.tail
            let str = ''

            while (current) {
                str += current.data + ''
                current = current.prev
            }

            return str
        }
        doublyLinkedList.prototype.backwardString = function () {
            let current = this.head
            let str = ''

            while (current) {
                str += current.data + ''
                current = current.next
            }

            return str
        }

        doublyLinkedList.prototype.insert = function (position, data) {
            if (position < 0 || position > this.length) return false
            let newNode = new Node(data)
            if (this.length === 0) {
                this.head = newNode
                this.tail = newNode
            } else {
                if (position == 0) {
                    this.head.prev = newNode
                    newNode.next = this.head
                    this.head = newNode
                } else if (position == this.length) {
                    newNode.prev = this.tail
                    this.tail.next = newNode
                    this.tail = newNode
                } else {
                    let current = this.head
                    let index = 0
                    while( index++ < position){
                        current = current.next
                    }
                    newNode.next = current
                    newNode.prev = current.prev
                    current.prev.next = newNode
                    current.prev = newNode

                }

            }

            this.length++
            return true
        }
        doublyLinkedList.prototype.get = function (position) {
            if (position < 0 || position >= this.length) return null
            let current = this.head
            let index = 0
            while (index++) {
                current = current.next
            }

            return current.data
        }
        doublyLinkedList.prototype.indexOf = function (data) {
            let current = this.head
            let index = 0
            while (current) {
                if (current.data === data) {
                    return index
                }
                current = current.next
                index++
            }
            return  -1
        }
        doublyLinkedList.prototype.update = function (position, newData) {
            if (position < 0 || position >= this.length) return false
            let current = this.head
            let index = 0
            while(index++ < position){
                current = current.next
            }
            current.data = newData
            return true
        }
        doublyLinkedList.prototype.removeAt = function (position) {
            if (position < 0 || position >= this.length) return null
            let current = this.head
            if (this.length === 1) {
                this.head = null
                this.tail = null
            } else {
                if (position === 0) { // 删除第一个节点
                    this.head.next.prev = null
                    this.head = this.head.next
                } else if (position === this.length - 1) { // 删除最后一个节点
                    this.tail.prev.next = null
                    this.tail = this.tail.prev
                } else {
                    let index = 0
                    while (index++ < position) {
                        current = current.next
                    }
                    current.prev.next = current.next
                    current.next.prev = current.prev
                }
            }
            this.length--
            return current.data
        }
        doublyLinkedList.prototype.remove = function (data) {
            let index = this.indexOf(data)
            return this.removeAt(index)
        }
    }


感谢你的阅读~
————————————————
版权声明:本文为CSDN博主「重庆崽儿Brand」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/brand2014/java/article/details/106134844



认识 ESLint 和 Prettier

seo达人

ESLint

先说是什么:ESLint 是一个检查代码质量与风格的工具,配置一套规则,他就能检查出你代码中不符合规则的地方,部分问题支持自动修复。


使用这么一套规则有什么用呢?如果单人开发的话倒是没什么了,但是一个团队若是存在两种风格,那格式化之后处理代码冲突就真的要命了,统一的代码风格真的很重要!


(其实以前自己做一个项目的时候,公司电脑和家庭电脑的代码风格配置不一样,在家加班的时候也经常顺手格式化了,这么循环了几次不同的风格,导致 diff 极其混乱

JavaScript中的Event Loop(事件循环)机制

seo达人

事件循环

JavaScript是单线程,非阻塞的

浏览器的事件循环


执行栈和事件队列

宏任务和微任务

node环境下的事件循环


和浏览器环境有何不同

事件循环模型

宏任务和微任务

经典题目分析

1. JavaScript是单线程,非阻塞的

单线程:


JavaScript的主要用途是与用户互动,以及操作DOM。如果它是多线程的会有很多复杂的问题要处理,比如有两个线程同时操作DOM,一个线程删除了当前的DOM节点,一个线程是要操作当前的DOM阶段,最后以哪个线程的操作为准?为了避免这种,所以JS是单线程的。即使H5提出了web worker标准,它有很多限制,受主线程控制,是主线程的子线程。


非阻塞:通过 event loop 实现。


2. 浏览器的事件循环

执行栈和事件队列

为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲 《Help, I'm stuck in an event-loop》)

Help, I'm stuck in an event-loop


执行栈: 同步代码的执行,按照顺序添加到执行栈中


function a() {

   b();

   console.log('a');

}

function b() {

   console.log('b')

}

a();

我们可以通过使用 Loupe(Loupe是一种可视化工具,可以帮助您了解JavaScript的调用堆栈/事件循环/回调队列如何相互影响)工具来了解上面代码的执行情况。


调用情况


执行函数 a()先入栈

a()中先执行函数 b() 函数b() 入栈

执行函数b(), console.log('b') 入栈

输出 b, console.log('b')出栈

函数b() 执行完成,出栈

console.log('a') 入栈,执行,输出 a, 出栈

函数a 执行完成,出栈。

事件队列: 异步代码的执行,遇到异步事件不会等待它返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当异步事件返回结果,将它放到事件队列中,被放入事件队列不会立刻执行起回调,而是等待当前执行栈中所有任务都执行完毕,主线程空闲状态,主线程会去查找事件队列中是否有任务,如果有,则取出排在第一位的事件,并把这个事件对应的回调放到执行栈中,然后执行其中的同步代码。


我们再上面代码的基础上添加异步事件,


function a() {

   b();

   console.log('a');

}

function b() {

   console.log('b')

   setTimeout(function() {

       console.log('c');

   }, 2000)

}

a();

此时的执行过程如下

img


我们同时再加上点击事件看一下运行的过程


$.on('button', 'click', function onClick() {

   setTimeout(function timer() {

       console.log('You clicked the button!');    

   }, 2000);

});


console.log("Hi!");


setTimeout(function timeout() {

   console.log("Click the button!");

}, 5000);


console.log("Welcome to loupe.");

img


简单用下面的图进行一下总结


执行栈和事件队列


宏任务和微任务

为什么要引入微任务,只有一种类型的任务不行么?


页面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级的任务需要尽快执行,那么一种类型的任务就不合适了,所以引入了微任务队列。


不同的异步任务被分为:宏任务和微任务

宏任务:


script(整体代码)

setTimeout()

setInterval()

postMessage

I/O

UI交互事件

微任务:


new Promise().then(回调)

MutationObserver(html5 新特性)

运行机制

异步任务的返回结果会被放到一个任务队列中,根据异步事件的类型,这个事件实际上会被放到对应的宏任务和微任务队列中去。


在当前执行栈为空时,主线程会查看微任务队列是否有事件存在


存在,依次执行队列中的事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的事件,把当前的回调加到当前指向栈。

如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;

当前执行栈执行完毕后时会立刻处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。


在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:


执行一个宏任务(栈中没有就从事件队列中获取)

执行过程中如果遇到微任务,就将它添加到微任务的任务队列中

宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

简单总结一下执行的顺序:

执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。


宏任务和微任务


深入理解js事件循环机制(浏览器篇) 这边文章中有个特别形象的动画,大家可以看着理解一下。


console.log('start')


setTimeout(function() {

 console.log('setTimeout')

}, 0)


Promise.resolve().then(function() {

 console.log('promise1')

}).then(function() {

 console.log('promise2')

})


console.log('end')

浏览器事件循环


全局代码压入执行栈执行,输出 start

setTimeout压入 macrotask队列,promise.then 回调放入 microtask队列,最后执行 console.log('end'),输出 end

调用栈中的代码执行完成(全局代码属于宏任务),接下来开始执行微任务队列中的代码,执行promise回调,输出 promise1, promise回调函数默认返回 undefined, promise状态变成 fulfilled ,触发接下来的 then回调,继续压入 microtask队列,此时产生了新的微任务,会接着把当前的微任务队列执行完,此时执行第二个 promise.then回调,输出 promise2

此时,microtask队列 已清空,接下来会会执行 UI渲染工作(如果有的话),然后开始下一轮 event loop, 执行 setTimeout的回调,输出 setTimeout

最后的执行结果如下


start

end

promise1

promise2

setTimeout

node环境下的事件循环

和浏览器环境有何不同

表现出的状态与浏览器大致相同。不同的是 node 中有一套自己的模型。node 中事件循环的实现依赖 libuv 引擎。Node的事件循环存在几个阶段。


如果是node10及其之前版本,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask队列中的任务。


node版本更新到11之后,Event Loop运行原理发生了变化,一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列,跟浏览器趋于一致。下面例子中的代码是按照的去进行分析的。


事件循环模型

┌───────────────────────┐

┌─>│        timers         │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     I/O callbacks     │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     idle, prepare     │

│  └──────────┬────────────┘      ┌───────────────┐

│  ┌──────────┴────────────┐      │   incoming:   │

│  │         poll          │<──connections───     │

│  └──────────┬────────────┘      │   data, etc.  │

│  ┌──────────┴────────────┐      └───────────────┘

│  │        check          │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

└──┤    close callbacks    │

  └───────────────────────┘

事件循环各阶段详解

node中事件循环的顺序


外部输入数据 --> 轮询阶段(poll) --> 检查阶段(check) --> 关闭事件回调阶段(close callback) --> 定时器检查阶段(timer) --> I/O 事件回调阶段(I/O callbacks) --> 闲置阶段(idle, prepare) --> 轮询阶段...


这些阶段大致的功能如下:


定时器检测阶段(timers): 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。

I/O事件回调阶段(I/O callbacks): 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。

闲置阶段(idle, prepare): 这个阶段仅在内部使用,可以不必理会

轮询阶段(poll): 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。

检查阶段(check): setImmediate()的回调会在这个阶段执行。

关闭事件回调阶段(close callbacks): 例如socket.on('close', ...)这种close事件的回调

poll:

这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。

这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。

check:

该阶段执行setImmediate()的回调函数。


close:

该阶段执行关闭请求的回调函数,比如socket.on('close', ...)。


timer阶段:

这个是定时器阶段,处理setTimeout()和setInterval()的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。


I/O callback阶段:

除了以下的回调函数,其他都在这个阶段执行:


setTimeout()和setInterval()的回调函数

setImmediate()的回调函数

用于关闭请求的回调函数,比如socket.on('close', ...)

宏任务和微任务

宏任务:


setImmediate

setTimeout

setInterval

script(整体代码)

I/O 操作等。

微任务:


process.nextTick

new Promise().then(回调)

Promise.nextTick, setTimeout, setImmediate的使用场景和区别

Promise.nextTick

process.nextTick 是一个独立于 eventLoop 的任务队列。

在每一个 eventLoop 阶段完成后会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行。

是所有异步任务中最快执行的。


setTimeout:

setTimeout()方法是定义一个回调,并且希望这个回调在我们所指定的时间间隔后第一时间去执行。


setImmediate:

setImmediate()方法从意义上将是立刻执行的意思,但是实际上它却是在一个固定的阶段才会执行回调,即poll阶段之后。


经典题目分析

一. 下面代码输出什么

async function async1() {

   console.log('async1 start');

   await async2();

   console.log('async1 end');

}

async function async2() {

   console.log('async2');

}

console.log('script start');

setTimeout(function() {

   console.log('setTimeout');

}, 0)

async1();

new Promise(function(resolve) {

   console.log('promise1');

   resolve();

}).then(function() {

   console.log('promise2');

});

console.log('script end');

先执行宏任务(当前代码块也算是宏任务),然后执行当前宏任务产生的微任务,然后接着执行宏任务


从上往下执行代码,先执行同步代码,输出 script start

遇到setTimeout,现把 setTimeout 的代码放到宏任务队列中

执行 async1(),输出 async1 start, 然后执行 async2(), 输出 async2,把 async2() 后面的代码 console.log('async1 end')放到微任务队列中

接着往下执行,输出 promise1,把 .then()放到微任务队列中;注意Promise本身是同步的立即执行函数,.then是异步执行函数

接着往下执行, 输出 script end。同步代码(同时也是宏任务)执行完成,接下来开始执行刚才放到微任务中的代码

依次执行微任务中的代码,依次输出 async1 end、 promise2, 微任务中的代码执行完成后,开始执行宏任务中的代码,输出 setTimeout

最后的执行结果如下


script start

async1 start

async2

promise1

script end

async1 end

promise2

setTimeout

二. 下面代码输出什么

console.log('start');

setTimeout(() => {

   console.log('children2');

   Promise.resolve().then(() => {

       console.log('children3');

   })

}, 0);


new Promise(function(resolve, reject) {

   console.log('children4');

   setTimeout(function() {

       console.log('children5');

       resolve('children6')

   }, 0)

}).then((res) => {

   console.log('children7');

   setTimeout(() => {

       console.log(res);

   }, 0)

})

这道题跟上面题目不同之处在于,执行代码会产生很多个宏任务,每个宏任务中又会产生微任务


从上往下执行代码,先执行同步代码,输出 start

遇到setTimeout,先把 setTimeout 的代码放到宏任务队列①中

接着往下执行,输出 children4, 遇到setTimeout,先把 setTimeout 的代码放到宏任务队列②中,此时.then并不会被放到微任务队列中,因为 resolve是放到 setTimeout中执行的

代码执行完成之后,会查找微任务队列中的事件,发现并没有,于是开始执行宏任务①,即第一个 setTimeout, 输出 children2,此时,会把 Promise.resolve().then放到微任务队列中。

宏任务①中的代码执行完成后,会查找微任务队列,于是输出 children3;然后开始执行宏任务②,即第二个 setTimeout,输出 children5,此时将.then放到微任务队列中。

宏任务②中的代码执行完成后,会查找微任务队列,于是输出 children7,遇到 setTimeout,放到宏任务队列中。此时微任务执行完成,开始执行宏任务,输出 children6;

最后的执行结果如下


start

children4

children2

children3

children5

children7

children6

三. 下面代码输出什么

const p = function() {

   return new Promise((resolve, reject) => {

       const p1 = new Promise((resolve, reject) => {

           setTimeout(() => {

               resolve(1)

           }, 0)

           resolve(2)

       })

       p1.then((res) => {

           console.log(res);

       })

       console.log(3);

       resolve(4);

   })

}



p().then((res) => {

   console.log(res);

})

console.log('end');

执行代码,Promise本身是同步的立即执行函数,.then是异步执行函数。遇到setTimeout,先把其放入宏任务队列中,遇到p1.then会先放到微任务队列中,接着往下执行,输出 3

遇到 p().then 会先放到微任务队列中,接着往下执行,输出 end

同步代码块执行完成后,开始执行微任务队列中的任务,首先执行 p1.then,输出 2, 接着执行p().then, 输出 4

微任务执行完成后,开始执行宏任务,setTimeout, resolve(1),但是此时 p1.then已经执行完成,此时 1不会输出。

最后的执行结果如下


3

end

2

4

你可以将上述代码中的 resolve(2)注释掉, 此时 1才会输出,输出结果为 3 end 4 1。


const p = function() {

   return new Promise((resolve, reject) => {

       const p1 = new Promise((resolve, reject) => {

           setTimeout(() => {

               resolve(1)

           }, 0)

       })

       p1.then((res) => {

           console.log(res);

       })

       console.log(3);

       resolve(4);

   })

}



p().then((res) => {

   console.log(res);

})

console.log('end');

3

end

4

1

最后强烈推荐几个非常好的讲解 event loop 的视频:


What the heck is the event loop anyway? | Philip Roberts | JSConf EU

Jake Archibald: In The Loop - JSConf.Asia

小程序入门到精通:了解小程序开发4个重要文件

前端达人

点击查看原图


1. 小程序没有DOM对象,一切基于组件化

2. 小程序的四个重要的文件

  • *.js —> view逻辑 —> javascript
  • *.wxml —> view结构 ----> html
  • *.wxss —> view样式 -----> css
  • *. json ----> view 数据 -----> json文件

注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。

2.1 WXML

WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件事件系统,可以构建出页面的结构。WXML 充当的就是类似 HTML 的角色
要完整了解 WXML 语法,请参考WXML 语法参考

2.2 WXSS

WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。

WXSS 用来决定 WXML 的组件应该怎么显示。

为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。

与 CSS 相比,WXSS 扩展的特性有:



尺寸单位

样式导入

2.3 json

JSON 是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。



全局配置

小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。



页面配置

每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项。



工具配置 project.config.json

通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。

考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个 project.config.json,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动

注意:

JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据。JSON的Key必须包裹在一个双引号中,在实践中,编写 JSON 的时候,忘了给 Key 值加双引号或者是把双引号写成单引号是常见错误。

JSON的值只能是以下几种数据格式,其他任何格式都会触发报错,例如 JavaScript 中的 undefined。



数字,包含浮点数和整数

字符串,需要包裹在双引号中

Bool值,true 或者 false

数组,需要包裹在方括号中 []

对象,需要包裹在大括号中 {}

Null

还需要注意的是 JSON 文件中无法使用注释,试图添加注释将会引发报错。


2.4 js

一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作。


注册页面

对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等



使用 Page 构造器注册页面

简单的页面可以使用 Page() 进行构造。



使用 Component 构造器构造页面

Page 构造器适用于简单的页面。但对于复杂的页面, Page 构造器可能并不好用。

此时,可以使用 Component 构造器来构造页面。 Component 构造器的主要区别是:方法需要放在 methods: { } 里面。

————————————————

版权声明:本文为CSDN博主「前端岚枫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yilanyoumeng3/java/article/details/106292742





2020年,令人惊叹的Echarts!

前端达人

点击查看原图


1.可视化面板介绍

应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生产数据,用户数据)下使用,可视化图表来展示体现数据,让数据更加直观,数据特点更加突出。

01-技术要点

  1. div + css 布局
  2. flex 布局
  3. Less
  4. 原生js + jquery 使用
  5. rem适配
  6. echarts基础

02-案例适配方案

  1. 设计稿是1920px
  2. flexible.js 把屏幕分为 24 等份
  3. cssrem 插件的基准值是 80px
    插件-配置按钮—配置扩展设置–Root Font Size 里面 设置。
    但是别忘记重启vscode软件保证生效


03-页面主体布局

  1. header布局
  2. mainbox布局
  3. 公共面板模块 panel
  4. 柱形图 bar
因为我们今天的主题是echarts部分所以前面的这些,我就为大家写好框架,里面的布局相信以大家的能力都是分分钟解决的事情。


2.Echarts(重点)

echarts介绍

常见的数据可视化库:

D3.js 目前 Web 端评价最高的 Javascript 可视化工具库(入手难)
ECharts.js 百度出品的一个开源 Javascript 数据可视化库
Highcharts.js 国外的前端数据可视化库,非商用免费,被许多国外大公司所使用
AntV 蚂蚁金服全新一代数据可视化解决方案 等等
Highcharts 和 Echarts 就像是 Office 和 WPS 的关系

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

官网地址:https://www.echartsjs.com/zh/index.html

echarts体验
下载echarts https://github.com/apache/incubator-echarts/tree/4.5.0

使用步骤(5大步骤):
1.引入echarts 插件文件到html页面中
2.准备一个具备大小的DOM容器

<div id="main" style="width: 600px;height:400px;"></div>

3.初始化echarts实例对象

var myChart = echarts.init(document.getElementById('main'));

4.指定配置项和数据(option)

var option = {
    xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {
        type: 'value'
    },
    series: [{
        data: [820, 932, 901, 934, 1290, 1330, 1320],
        type: 'line'
    }]
};

5.将配置项设置给echarts实例对象


myChart.setOption(option);  


echarts基础配置

这是要求同学们知道以下配置每个模块的主要作用干什么的就可以了

series

系列列表。每个系列通过 type 决定自己的图表类型

大白话:图标数据,指定什么类型的图标,可以多个图表重叠。

xAxis:直角坐标系 grid 中的 x 轴

boundaryGap: 坐标轴两边留白策略 true,这时候刻度只是作为分隔线,标签和数据点都会在两个刻度之间的带(band)中间。

yAxis:直角坐标系 grid 中的 y 轴

grid:直角坐标系内绘图网格。

title:标题组件

tooltip:提示框组件

legend:图例组件

color:调色盘颜色列表

数据堆叠,同个类目轴上系列配置相同的stack值后 后一个系列的值会在前一个系列的值上相加。



option = {

    // color设置我们线条的颜色 注意后面是个数组

    color: ['pink', 'red', 'green', 'skyblue'],

    // 设置图表的标题

    title: {

        text: '折线图堆叠123'

    },

    // 图表的提示框组件 

    tooltip: {

        // 触发方式

        trigger: 'axis'

    },

    // 图例组件

    legend: {

       // series里面有了 name值则 legend里面的data可以删掉

    },

    // 网格配置  grid可以控制线形图 柱状图 图表大小

    grid: {

        left: '3%',

        right: '4%',

        bottom: '3%',

        // 是否显示刻度标签 如果是true 就显示 否则反之

        containLabel: true

    },

    // 工具箱组件  可以另存为图片等功能

    toolbox: {

        feature: {

            saveAsImage: {}

        }

    },

    // 设置x轴的相关配置

    xAxis: {

        type: 'category',

        // 是否让我们的线条和坐标轴有缝隙

        boundaryGap: false,

        data: ['星期一', '周二', '周三', '周四', '周五', '周六', '周日']

    },

     // 设置y轴的相关配置

    yAxis: {

        type: 'value'

    },

    // 系列图表配置 它决定着显示那种类型的图表

    series: [

        {

            name: '邮件营销',

            type: 'line',

           

            data: [120, 132, 101, 134, 90, 230, 210]

        },

        {

            name: '联盟广告',

            type: 'line',



            data: [220, 182, 191, 234, 290, 330, 310]

        },

        {

            name: '视频广告',

            type: 'line',

          

            data: [150, 232, 201, 154, 190, 330, 410]

        },

        {

            name: '直接访问',

            type: 'line',

          

            data: [320, 332, 301, 334, 390, 330, 320]

        }

    ]

};



3.Echarts快速使用

1.官网实例

点击查看原图



官网默认为我们提供了大量的案例,我们需要使用那一种只需要直接点击打开后复制他们的相关配置,然后按照我上面说的5大步骤进行使用即可。

2.社区Gallery

点击查看原图



官方自带的图例,可能在很多时候并不能满足我们的需要,在社区这里可以找到一些基于echart的高度定制好的图表,相当于基于jquery开发的插件,这里是基于echarts开发的第三方的图表。

本案例中使用了地图模拟飞行的案例就是从社区中进行引用的,
参考社区的例子:https://gallery.echartsjs.com/editor.html?c=x0-ExSkZDM (模拟飞机航线)
实现步骤:

第一需要下载china.js提供中国地图的js文件
第二个因为里面代码比较多,我们新建一个新的js文件 myMap.js 引入
使用社区提供的配置即可。
代码已经上传至我的码云如有需要的小伙伴可自行下载:
https://gitee.com/jiuyueqi/echarts

ps:最后呢,如果大家看完楼主的文章觉得对echarts的学习和了解有所帮助,麻烦大家路过点个赞点个关注呗!楼主后续还会继续更新有关前端方面的面试题资料或者其他方面的知识。
————————————————
版权声明:本文为CSDN博主「程序猿玖月柒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45257157/java/article/details/106300587

关于JavaScript获取时间函数及实现倒计时

前端达人

JavaScript数组对象的迭代方法详解

上一篇博客讲到了数组的方法,当然里边比较复杂的就是数组的迭代方法,因为涉及到了回调函数,所以这篇博客我们来详细讲解一下js数组迭代方法的使用。


1.forEach(funcrion(value,index,arr){}):对数组的每一项运行给定函数,这个方法不进行返回,所以一般用于让数组循环执行某方法。

  var arr=[1,2,3,4,5,6];

    arr.forEach(function(val,index,arr){

        console.log(val,index,arr);

    })

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组


注意:forEach()方法不返回值,所以回调函数中使用return会打印出来undefined

2.map(funcrion(value,index,arr){}):对数组的每一项运行给定函数,它将返回执行函数后的结果组成的新数组。

 var aNum2 = [1.2, 1.8, 2.0, 4.3];

    console.log(aNum2.map(Math.floor()));// [1,1,2,4]

    

    var arr=[1,2,3];

    console.log(arr.map(function(val,index){

        return val*3

    }));// 3 6 9

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组

注意:map()方法有返回值,返回值为新的数组,所以可以直接再回调函数中return

3.every(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进项判断,若对于每项执行函数都返回了true,则其结果为true。

 var arr=[10,20,30];

    console.log(arr.every(function(val){

        return val>20;

    }));// false

    

    console.log(arr.every(function(val){

        return val>0;

    }));// true

    

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组



注意:every()方法所有的数组项都符合判断时返回true,否则返回false。

4.some(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进行判断,若存在一项符合条件的数组项,则其结果为true。

    var arr=[10,20,30];

    console.log(arr.some(function(val){

        return val>20;

    }));// true

    

    console.log(arr.some(function(val){

        return val>0;

    }));// true

    

    console.log(arr.some(function(val){

        return val<0;

    }));// false

    

    arr.some(function(val){

        console.log(val<0);

    });//fasle false false

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组


注意:some()方法如果回调函数执行完会根据结果返回true或false,但是回调函数中打印判断是,只会作为判断条件的返回值,则会打印多遍。

5.fliter(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进行过滤,将符合条件的数组项添加到新的数组中,并返回新的数组。

   var aNum=[1,2,3,4];
    console.log(aNum.filter(function (num) {
        return num > 1;
    }));//[2,3,4,]
    aNum.filter(function (num) {
        console.log(num > 1);//true true true
    })

注意:filter()方法对数组项进行过滤,然后将符合条件的数组项添加到一个新的数组并返回,但是如果直接打印这个判断条件,相当于打印的判断条件的结果,只会返回true或者false。

6.ES6中新增的迭代方法

1.find():返回第一个符合传入测试(函数)条件的数组元素。


  var aNum=[10,20,30,40];

    console.log(aNum.find(function (num) {

        return num > 19;

    }));//1

    console.log(aNum.find(function (num) {

        return num < 0;

    }));//undefined



2.findIndex():返回符合传入测试(函数)条件的数组元素索引。


console.log(aNum.findIndex(function (num) { return num > 19; }));//3


3.includes():判断一个数组是否包含一个指定的值。

总结:

forEach()与map()是一对,用于数组遍历执行指定函数,前者不返回数组,后者返回 处理过的新数组。
every()与some()是一对,分别适用于检测数组是否全部满足某条件或者存在满足的数组项,返回true或false。
filter()则是相当于过滤器的存在,过滤掉数组中不符合条件的数据,将符合条件的数组项添加到新数组,并返回。
————————————————
版权声明:本文为CSDN博主「Mr_Han119」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39155611/java/article/details/106294417


了不起的 tsconfig.json 指南

seo达人

在 TypeScript 开发中,tsconfig.json 是个不可或缺的配置文件,它是我们在 TS 项目中最常见的配置文件,那么你真的了解这个文件吗?它里面都有哪些优秀配置?如何配置一个合理的 tsconfig.json 文件?本文将全面带大家一起详细了解 tsconfig.json 的各项配置。



本文将从以下几个方面全面介绍 tsconfig.json 文件:

了不起的 tsconfig.json 指南.png




水平有限,欢迎各位大佬指点~~


一、tsconfig.json 简介


1. 什么是 tsconfig.json

TypeScript 使用 tsconfig.json 文件作为其配置文件,当一个目录中存在 tsconfig.json 文件,则认为该目录为 TypeScript 项目的根目录。

通常 tsconfig.json 文件主要包含两部分内容:指定待编译文件和定义编译选项。



从《TypeScript编译器的配置文件的JSON模式》可知,目前 tsconfig.json 文件有以下几个顶层属性:


compileOnSave

compilerOptions

exclude

extends

files

include

references

typeAcquisition


文章后面会详细介绍一些常用属性配置。



2. 为什么使用 tsconfig.json

通常我们可以使用 tsc 命令来编译少量 TypeScript 文件:


/*

 参数介绍:

 --outFile // 编译后生成的文件名称

 --target  // 指定ECMAScript目标版本

 --module  // 指定生成哪个模块系统代码

 index.ts  // 源文件

*/

$ tsc --outFile leo.js --target es3 --module amd index.ts

但如果实际开发的项目,很少是只有单个文件,当我们需要编译整个项目时,就可以使用 tsconfig.json 文件,将需要使用到的配置都写进 tsconfig.json 文件,这样就不用每次编译都手动输入配置,另外也方便团队协作开发。



二、使用 tsconfig.json

目前使用 tsconfig.json 有2种操作:


1. 初始化 tsconfig.json

在初始化操作,也有 2 种方式:


手动在项目根目录(或其他)创建 tsconfig.json 文件并填写配置;

通过 tsc --init 初始化 tsconfig.json 文件。


2. 指定需要编译的目录

在不指定输入文件的情况下执行 tsc 命令,默认从当前目录开始编译,编译所有 .ts 文件,并且从当前目录开始查找 tsconfig.json 文件,并逐级向上级目录搜索。


$ tsc

另外也可以为 tsc 命令指定参数 --project 或 -p 指定需要编译的目录,该目录需要包含一个 tsconfig.json 文件,如:


/*

 文件目录:

 ├─src/

 │  ├─index.ts

 │  └─tsconfig.json

 ├─package.json

*/

$ tsc --project src

注意,tsc 的命令行选项具有优先级,会覆盖 tsconfig.json 中的同名选项。



更多 tsc 编译选项,可查看《编译选项》章节。



三、使用示例

这个章节,我们将通过本地一个小项目 learnTsconfig 来学着实现一个简单配置。

当前开发环境:windows / node 10.15.1 / TypeScript3.9



1. 初始化 learnTsconfig 项目

执行下面命令:


$ mkdir learnTsconfig

$ cd .\learnTsconfig\

$ mkdir src

$ new-item index.ts

并且我们为 index.ts 文件写一些简单代码:


// 返回当前版本号

function getVersion(version:string = "1.0.0"): string{

   return version;

}


console.log(getVersion("1.0.1"))

我们将获得这么一个目录结构:


 └─src/

    └─index.ts


2. 初始化 tsconfig.json 文件

在 learnTsconfig 根目录执行:


$ tsc --init


3. 修改 tsconfig.json 文件

我们设置几个常见配置项:


{

 "compilerOptions": {

   "target": "ES5",             // 目标语言的版本

   "module": "commonjs",        // 指定生成代码的模板标准

   "noImplicitAny": true,       // 不允许隐式的 any 类型

   "removeComments": true,      // 删除注释

   "preserveConstEnums": true,  // 保留 const 和 enum 声明

   "sourceMap": true            // 生成目标文件的sourceMap文件

 },

 "files": [   // 指定待编译文件

   "./src/index.ts"  

 ]

}

其中需要注意一点:

files 配置项值是一个数组,用来指定了待编译文件,即入口文件。

当入口文件依赖其他文件时,不需要将被依赖文件也指定到 files 中,因为编译器会自动将所有的依赖文件归纳为编译对象,即 index.ts 依赖 user.ts 时,不需要在 files 中指定 user.ts , user.ts 会自动纳入待编译文件。



4. 执行编译

配置完成后,我们可以在命令行执行 tsc 命令,执行编译完成后,我们可以得到一个 index.js 文件和一个 index.js.map 文件,证明我们编译成功,其中 index.js 文件内容如下:


function getVersion(version) {

   if (version === void 0) { version = "1.0.0"; }

   return version;

}

console.log(getVersion("1.0.1"));

//# sourceMappingURL=index.js.map

可以看出,tsconfig.json 中的 removeComments 配置生效了,将我们添加的注释代码移除了。



到这一步,就完成了这个简单的示例,接下来会基于这个示例代码,讲解《七、常见配置示例》。



四、tsconfig.json 文件结构介绍


1. 按顶层属性分类

在 tsconfig.json 文件中按照顶层属性,分为以下几类:

tsconfig.json 文件结构(顶层属性).png


了不起的 tsconfig.json 指南.png



2. 按功能分类

tsconfig.json 文件结构(功能).png




五、tsconfig.json 配置介绍


1. compileOnSave

compileOnSave 属性作用是设置保存文件的时候自动编译,但需要编译器支持。


{

   // ...

 "compileOnSave": false,

}


2. compilerOptions

compilerOptions 属性作用是配置编译选项。

若 compilerOptions 属性被忽略,则编译器会使用默认值,可以查看《官方完整的编译选项列表》。

编译选项配置非常繁杂,有很多配置,这里只列出常用的配置。


{

 // ...

 "compilerOptions": {

   "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度

   "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置

   "diagnostics": true, // 打印诊断信息

   "target": "ES5", // 目标语言的版本

   "module": "CommonJS", // 生成代码的模板标准

   "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",

   "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",

   "allowJS": true, // 允许编译器编译JS,JSX文件

   "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用

   "outDir": "./dist", // 指定输出目录

   "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构

   "declaration": true, // 生成声明文件,开启后会自动生成声明文件

   "declarationDir": "./file", // 指定生成声明文件存放目录

   "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件

   "sourceMap": true, // 生成目标文件的sourceMap文件

   "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中

   "declarationMap": true, // 为声明文件生成sourceMap

   "typeRoots": [], // 声明文件目录,默认时node_modules/@types

   "types": [], // 加载的声明文件包

   "removeComments":true, // 删除注释

   "noEmit": true, // 不输出文件,即编译后不会生成任何js文件

   "noEmitOnError": true, // 发送错误时不输出任何文件

   "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用

   "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块

   "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现

   "strict": true, // 开启所有严格的类型检查

   "alwaysStrict": true, // 在代码中注入'use strict'

   "noImplicitAny": true, // 不允许隐式的any类型

   "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量

   "strictFunctionTypes": true, // 不允许函数参数双向协变

   "strictPropertyInitialization": true, // 类的实例属性必须初始化

   "strictBindCallApply": true, // 严格的bind/call/apply检查

   "noImplicitThis": true, // 不允许this有隐式的any类型

   "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)

   "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)

   "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)

   "noImplicitReturns": true, //每个分支都会有返回值

   "esModuleInterop": true, // 允许export=导出,由import from 导入

   "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块

   "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入

   "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录

   "paths": { // 路径映射,相对于baseUrl

     // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置

     "jquery": ["node_modules/jquery/dist/jquery.min.js"]

   },

   "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错

   "listEmittedFiles": true, // 打印输出文件

   "listFiles": true// 打印编译的文件(包括引用的声明文件)

 }

}


3. exclude

exclude 属性作用是指定编译器需要排除的文件或文件夹。

默认排除 node_modules 文件夹下文件。


{

   // ...

 "exclude": [

   "src/lib" // 排除src目录下的lib文件夹下的文件不会编译

 ]

}

和 include 属性一样,支持 glob 通配符:


* 匹配0或多个字符(不包括目录分隔符)

? 匹配一个任意字符(不包括目录分隔符)

**/ 递归匹配任意子目录


4. extends

extends 属性作用是引入其他配置文件,继承配置。

默认包含当前目录和子目录下所有 TypeScript 文件。


{

   // ...

 // 把基础配置抽离成tsconfig.base.json文件,然后引入

   "extends": "./tsconfig.base.json"

}


5. files

files 属性作用是指定需要编译的单个文件列表。

默认包含当前目录和子目录下所有 TypeScript 文件。


{

   // ...

 "files": [

   // 指定编译文件是src目录下的leo.ts文件

   "scr/leo.ts"

 ]

}


6. include

include 属性作用是指定编译需要编译的文件或目录。


{

   // ...

 "include": [

   // "scr" // 会编译src目录下的所有文件,包括子目录

   // "scr/*" // 只会编译scr一级目录下的文件

   "scr/*/*" // 只会编译scr二级目录下的文件

 ]

}


7. references

references 属性作用是指定工程引用依赖。

在项目开发中,有时候我们为了方便将前端项目和后端node项目放在同一个目录下开发,两个项目依赖同一个配置文件和通用文件,但我们希望前后端项目进行灵活的分别打包,那么我们可以进行如下配置:


{

   // ...

 "references": [ // 指定依赖的工程

    {"path": "./common"}

 ]

}


8. typeAcquisition

typeAcquisition 属性作用是设置自动引入库类型定义文件(.d.ts)相关。

包含 3 个子属性:


enable  : 布尔类型,是否开启自动引入库类型定义文件(.d.ts),默认为 false;

include  : 数组类型,允许自动引入的库名,如:["jquery", "lodash"];

exculde  : 数组类型,排除的库名。

{

   // ...

 "typeAcquisition": {

   "enable": false,

   "exclude": ["jquery"],

   "include": ["jest"]

 }

}


六、常见配置示例

本部分内容中,我们找了几个实际开发中比较常见的配置,当然,还有很多配置需要自己摸索哟~~



1. 移除代码中注释

tsconfig.json:


{

 "compilerOptions": {

   "removeComments": true,

 }

}

编译前:


// 返回当前版本号

function getVersion(version:string = "1.0.0"): string{

   return version;

}

console.log(getVersion("1.0.1"))

编译结果:


function getVersion(version) {

   if (version === void 0) { version = "1.0.0"; }

   return version;

}

console.log(getVersion("1.0.1"));


2. 开启null、undefined检测

tsconfig.json:


{

   "compilerOptions": {

       "strictNullChecks": true

   },

}

修改 index.ts 文件内容:


const leo;

leo = new Pingan('leo','hello');


这时候编辑器也会提示错误信息,执行 tsc 后,控制台报错:


src/index.ts:9:11 - error TS2304: Cannot find name 'Pingan'.


9 leo = new Pingan('leo','hello');


Found 1 error.


3. 配置复用

通过 extends 属性实现配置复用,即一个配置文件可以继承另一个文件的配置属性。

比如,建立一个基础的配置文件 configs/base.json :


{

 "compilerOptions": {

   "noImplicitAny": true,

   "strictNullChecks": true

 }

}

在tsconfig.json 就可以引用这个文件的配置了:


{

 "extends": "./configs/base",

 "files": [

   "main.ts",

   "supplemental.ts"

 ]

}


4. 生成枚举的映射代码

在默认情况下,使用 const 修饰符后,枚举不会生成映射代码。

如下,我们可以看出:使用 const 修饰符后,编译器不会生成任何 RequestMethod 枚举的任何映射代码,在其他地方使用时,内联每个成员的值,节省很大开销。


const enum RequestMethod {

 Get,

 Post,

 Put,

 Delete

}


let methods = [

 RequestMethod.Get,

 RequestMethod.Post

]

编译结果:


"use strict";

let methods = [

   0 /* Get */,

   1 /* Post */

];

当然,我们希望生成映射代码时,也可以设置 tsconfig.json 中的配置,设置 preserveConstEnums 编译器选项为 true :


{

 "compilerOptions": {

   "target": "es5",

   "preserveConstEnums": true

 }

}


最后编译结果变成:


"use strict";

var RequestMethod;

(function (RequestMethod) {

   RequestMethod[RequestMethod["Get"] = 0] = "Get";

   RequestMethod[RequestMethod["Post"] = 1] = "Post";

   RequestMethod[RequestMethod["Put"] = 2] = "Put";

   RequestMethod[RequestMethod["Delete"] = 3] = "Delete";

})(RequestMethod || (RequestMethod = {}));

let methods = [

   0 /* Get */,

   1 /* Post */

];


5. 关闭 this 类型注解提示

通过下面代码编译后会报错:


const button = document.querySelector("button");

button?.addEventListener("click", handleClick);

function handleClick(this) {

console.log("Clicked!");

this.removeEventListener("click", handleClick);

}


报错内容:


src/index.ts:10:22 - error TS7006: Parameter 'this' implicitly has an 'any' type.

10 function handleClick(this) {

Found 1 error.


这是因为 this 隐式具有 any 类型,如果没有指定类型注解,编译器会提示“"this" 隐式具有类型 "any",因为它没有类型注释。”。



解决方法有2种:


指定 this 类型,如本代码中为 HTMLElement 类型:

HTMLElement 接口表示所有的 HTML 元素。一些HTML元素直接实现了 HTMLElement 接口,其它的间接实现HTMLElement接口。

关于 HTMLElement 可查看详细。


使用 --noImplicitThis 配置项:


在 TS2.0 还增加一个新的编译选项: --noImplicitThis,表示当 this 表达式值为 any 类型时生成一个错误信息。我们设置为 true 后就能正常编译。


{

 "compilerOptions": {

   "noImplicitThis": true

 }

}


七、Webpack/React 中使用示例


1. 配置编译 ES6 代码,JSX 文件

创建测试项目 webpack-demo,结构如下:


webpack-demo/

 |- package.json

 |- tsconfig.json

 |- webpack.config.js

 |- /dist

   |- bundle.js

   |- index.html

 |- /src

   |- index.js

   |- index.ts

 |- /node_modules

安装 TypeScript 和 ts-loader:


$ npm install --save-dev typescript ts-loader

配置 tsconfig.json,支持 JSX,并将 TypeScript 编译为 ES5:


{

 "compilerOptions": {

   "outDir": "./dist/",

   "noImplicitAny": true,

+   "module": "es6",

+   "target": "es5",

+   "jsx": "react",

   "allowJs": true

 }

}

还需要配置 webpack.config.js,使其能够处理 TypeScript 代码,这里主要在 rules 中添加 ts-loader :


const path = require('path');


module.exports = {

 entry: './src/index.ts',

 module: {

   rules: [

     {

       test: /\.tsx?$/,

       use: 'ts-loader',

       exclude: /node_modules/

     }

   ]

 },

 resolve: {

   extensions: [ '.tsx', '.ts', '.js' ]

 },

 output: {

   filename: 'bundle.js',

   path: path.resolve(__dirname, 'dist')

 }

};


2. 配置 source map

想要启用 source map,我们必须配置 TypeScript,以将内联的 source map 输出到编译后的 JavaScript 文件中。

只需要在 tsconfig.json 中配置 sourceMap 属性:


 {

   "compilerOptions": {

     "outDir": "./dist/",

+     "sourceMap": true,

     "noImplicitAny": true,

     "module": "commonjs",

     "target": "es5",

     "jsx": "react",

     "allowJs": true

   }

 }

然后配置 webpack.config.js 文件,让 webpack 提取 source map,并内联到最终的 bundle 中:


 const path = require('path');


 module.exports = {

   entry: './src/index.ts',

+   devtool: 'inline-source-map',

   module: {

     rules: [

       {

         test: /\.tsx?$/,

         use: 'ts-loader',

         exclude: /node_modules/

       }

     ]

   },

   resolve: {

     extensions: [ '.tsx', '.ts', '.js' ]

   },

   output: {

     filename: 'bundle.js',

     path: path.resolve(__dirname, 'dist')

   }

 };


八、总结

本文较全面介绍了 tsconfig.json 文件的知识,从“什么是 tsconfig.js 文件”开始,一步步带领大家全面认识 tsconfig.json 文件。

文中通过一个简单 learnTsconfig 项目,让大家知道项目中如何使用 tsconfig.json 文件。在后续文章中,我们将这么多的配置项进行分类学习。最后通过几个常见配置示例,解决我们开发中遇到的几个常见问题。

vue.js路由与vuex数据模型设计

seo达人

路由设计

本则路由考虑验证进入登录页面,完成登录操作进入首页。


import Vue from "vue";

import Router from "vue-router";

Vue.use(Router);


import store from "@/store/store";


// (延迟加载)

const Login = () => import("@/views/login");

const Home = () => import("@/views/home");


const HomeRoute = {

 path: "/",

 name: "首页",

 component: Home

};


export { HomeRoute };


const router = new Router({

 base: process.env.BASE_URL,

 routes: [

   {

     path: "/login",

     name: "登录",

     component: Login

   },

   HomeRoute

 ]

});


router.beforeEach((to, from, next) => {

 let loginName = store.state.user.loginName;

 if (to.path === "/" && loginName == "") {

   next("/login");

 } else {

   next();

 }

});


export default router;

数据模型

const state = {

 loginName: ""

};

const mutations = {

 SET_LOGINNAME(state, loginName) {

   state.loginName = loginName;

 }

};

const actions = {

 login({ commit }, userInfo) {

   return new Promise((res, ret) => {

     commit("SET_LOGINNAME", userInfo);

     res();

   });

 },

 logout({ commit }) {

   return new Promise((res, ret) => {

     commit("SET_LOGINNAME", "");

     res();

   });

 }

};

export default {

 namespaced: true,

 state,

 mutations,

 actions

};

import Vue from "vue";

import Vuex from "vuex";

Vue.use(Vuex);


import user from "./modules/user";


const store = new Vuex.Store({

 modules: {

   user

 }

});


export default store;

组件

<div class="modify">

 <input

   type="text"

   @keydown.enter.prevent="handleKeydown"

   v-model="currentVal"

   placeholder="使用enter键切换频道"

 />

 <button @click="reset" style="margin-left:5px;outline:none;cursor:pointer;">复位</button>

</div>

import { mapState, mapMutations, mapActions } from "vuex";

export default {

 name: "login",

 data() {

   return {

     currentVal: "",

     list: ["咨询服务", "音悦台", "体育台", "财经频道", "时尚资讯"],

     index: 0

   };

 },

 computed: {

   ...mapState({

     loginName: state => state.user.loginName

   })

 },

 methods: {

   ...mapActions({

     login: "user/login"

   }),

   handleToHome() {

     let userInfo = "user";

     this.login(userInfo);

     this.$router.push({

       path: "/"

     });

   },

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档