Fundebug

Fundebug 查看完整档案

厦门编辑中国科学技术大学  |  计算机系 编辑Fundebug  |  创始人 编辑 www.fundebug.com 编辑
编辑

一行代码搞定BUG监控:https://www.fundebug.com/

个人动态

Fundebug 发布了文章 · 2020-04-07

Fundebug前端异常监控插件更新至2.4.0,支持配置breadcrumbSize

摘要: 灵活配置 breadcrumbSize,记录更多用户行为数据。

Fundebug 前端异常监控服务

Fundebug提供专业的前端异常监控服务,我们的插件可以提供全方位的异常监控,可以帮助开发者第一时间定位各种前端异常,包括但不限于 JavaScript 执行错误以及 HTTP 请求错误。

并且,Fundebug 支持 Source Map 还原,记录用户行为以及“录制”用户操作视频,帮助开发者快速复现 BUG,提高 Debug 效率,欢迎大家免费试用~

Fundebug 前端异常监控插件更新至 2.4.0,支持配置 breadcrumbSize 和新增 leaveBreadcrumb()接口。

1、新增 breadcrumbSize 属性

在每一个错误事件的详情页,有一个“用户行为”的标签。用户行为记录了用户点击、用户输入、网络请求、页面跳转、控制台日志等行为数据。

breadcrumb指用户行为列表中的行为记录,用户行为数据的长度默认为 20 条。如果项目相对复杂,需要记录的数据量会更多,那么用户可以设置 breadcrumbSize 属性,来配置合理的长度。

fundebug.init({
    breadcrumbSize: 30,
});

在 HTML 中配置<script>标签中配置 apikey 属性

<script
    data-original="https://js.fundebug.cn/fundebug.2.4.0.min.js"
    breadcrumbSize="30"
></script>

2、新增 fundebug.leaveBreadcrumb()方法

为了方便更好地复现 bug,有时候需要更详尽的用户行为数据,我们可以在某些关键节点使用leaveBreadcrumb()进行手动埋点,获取的数据会加入到用户行为列表中。

fundebug.leaveBreadcrumb({
    message: "调用fetchData()接口",
    page: "商品详情页面",
});

捕获的异常的用户行为记录中会包含我们手动埋点的信息。

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了40亿+错误事件,付费客户有阳光保险、达令家、核桃编程、荔枝FM、微脉等众多品牌企业。

查看原文

赞 2 收藏 2 评论 0

Fundebug 发布了文章 · 2020-03-25

Fundebug支持配置实时报警

摘要: 将报警间隔设置为实时,第一时间接收提醒!

为了防止报警过于频繁,在项目设置的“报警规则”页面,我们对报警间隔做了限制,默认一个项目 30 分钟内最多报警一次。当然,时间可以调节,最少能调整到 15 分钟。然而在实际使用中,客户希望每一个错误都能够收到实时的报警。经过慎重考虑,我们决定放开限制,做到支持实时报警。

如果您配置了“实时”,我们建议:

  • 接入第三方报警(企业微信、钉钉等),做到真正意义上的实时;我们对邮件报警数量做了限制,每天累计超过 96 封则不再发送;第三方报警没有此限制;
  • 合理配置报警规则,防止报警过度;如果发现报警消息过于频繁,请及时调整报警规则;

另外,如果您的数据量很大,请谨慎选择实时报警,因为每一个错误都会发送一次报警提醒。假设一天新增 1 万个错误,那么将会收到 1 万条报警消息。

参考

查看原文

赞 1 收藏 1 评论 0

Fundebug 发布了文章 · 2020-03-02

使用Fundebug API 批量上传Source Map

摘要: 通过代码批量上传 Source Map,实现流程自动化!

Fundebug 支持使用 Source Map 还原真正的错误位置。这样的话,开发者能够迅速定位出错的源代码。另外,Fundebug 还能够展示出错的代码块,帮助开发者更快地解决问题。

Fundebug 支持哪些 Source Map 上传方式?

如果希望使用 Source Map 功能的话,我们必须拿到 Source Map 文件。用户可以将 Source Map 文件挂载到自己的服务器,我们会自动下载;或者,用户也可以主动上传 Source Map。我们一共提供了 3 种不同的上传方式:

  • 前端 UI 上传
  • fundebug-cli 批量上传
  • API 上传

前面两种方式都比较直观,文档中有详细的说明。接下里介绍如何通过代码调用 API 实现 Source Map 批量上传。

如何批量上传 Source Map?

Fundebug 支持通过POST请求上传 Source Map,接口为/javascript/sourcemap/upload, 参数包括

  • apikey: 获取 apikey 需要免费注册帐号并且创建项目。
  • appversion: 可选参数,用于配置应用版本。若希望区分不同版本的 Source Map,则在接入 Fundebug 时,必须配置对应的 appversion 属性,并在代码更新时及时更新。
  • sourceMap:Source Map 文件信息,具体内容请参考下方示例代码。

下面给出 Node.js 版本的代码供参考,

const request = require("request-promise");
const fs = require("fs");

const options = {
    method: "POST",
    uri: "https://fundebug.com/javascript/sourcemap/upload",
    formData: {
        apikey: "YOUR-API-KEY",
        appversion: "1.0.0",
        sourceMap: {
            value: fs.createReadStream("./data/app.6c20067a.js.map"),
            options: {
                filename: "app.6c20067a.js.map",
                contentType: "text"
            }
        }
    }
};

request(options)
    .then(function(success) {
        console.log("success:", success);
    })
    .catch(function(err) {
        console.log("fail:", err);
    });

如何清除已上传 Source Map?

如果上传的 Source Map 太多,希望将旧的 Source Map 文件删掉,我们也提供了相应的接口: /javascript/sourcemap/clear。示例代码如下:

const request = require("request-promise");
const fs = require("fs");

const options = {
    method: "POST",
    uri: "https://fundebug.com/javascript/sourcemap/clear",
    body: {
        apikey: "YOUR-API-KEY"
    },
    json: true
};

request(options)
    .then(function(success) {
        console.log("success:", success);
    })
    .catch(function(err) {
        console.log("fail:", err);
    });

感谢客户大大风变科技的反馈!

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了30亿+错误事件,付费客户有阳光保险、达令家、核桃编程、荔枝FM、微脉等众多品牌企业。欢迎大家免费试用

查看原文

赞 1 收藏 1 评论 0

Fundebug 赞了文章 · 2020-02-17

什么是 globalThis,为什么要学会使用它?

作者: Dmitri Pavlutin
译者:前端小智
来源:news
点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。


JS 语言越来越多被用于各种环境中。除了最常见的浏览器之外,它还可以在服务器、智能手机甚至机器人硬件上运行。

每个环境都有其自己的对象模型,并提供了不同的语法来访问全局对象。 例如,在 Web 浏览器中,可以通过windowselfframes访问全局对象。 但是,在 Node.js 中,这些属性不存在,而必须使用global。 在Web Worker中,只有self可用。

这些不同的全局对象引用方法让 JS 实现跨平台变得非常困难。幸运的是,有一个提案正在制定中,旨在通过引入一个名为globalThis的标准属性来解决这个问题,该属性将在所有环境中可用。

在本文中,我们首先研究一下 JS 环境中的全局对象,然后了解globalThis如何提供统一的机制来访问它。

window

window属性用于在浏览器环境中引用当前文档的全局对象。在代码的顶层,使用var关键字声明的变量将成为window的属性,并且可以从代码中的任何位置进行访问:

var a = [10, 20];

console.log(window.a);          // → [10, 20]
console.log(a === window.a);    // → true

通常,在使用window 的属性时不需要直接引用它,因为引用是隐式的。但是,当存在与全局变量同名的局部变量时,使用window是惟一的选择:

var a = 10;

(function() {
  var a = 20;   
  console.log(a);           // → 20
  console.log(window.a);    // → 10
})();

如上所看到的,window 对于引用全局对象非常有用,无论代码在哪个作用域内运行。注意,window实际上引用了自身:window.window,所以,window.window === window

除了标准的 JS 属性和方法外,window对象还包含几个额外的属性和方法,这些属性和方法允许我们控制web浏览器窗口和文档本身。

self

Web Workers API没有Window对象,因为它没有浏览上下文。相反,它提供WorkerGlobalScope接口,其中包含类似window携带的数据。

要访问Web Workers中的全局对象,我们使用self,它是window对象的同义词。与window类似,self是对全局对象的引用:

// a web worker
console.log(self);    // => DedicatedWorkerGlobalScope {...}

var a = 10;

console.log(self.a);          // → 10
console.log(a === self.a);    // → true

在浏览器环境中,此代码打印的window对象而不是DedicatedWorkerGlobalScope。 由于self的值会根据使用环境的不同而变化,所以有时它比window更可取。 selfWeb worker上下文中引用WorkerGlobalScope.self时,在浏览器上下文中引用window.self

不要将self属性与声明局部变量(用于维护对上下文的引用)的通用 JS 模式相混淆,这一点很重要。例如

const obj = {
  myProperty: 10,
  myMethod: function(){
    console.log(this === obj);    // => true

    // 将 this 值存储在变量中,以便在嵌套函数中使用
    const self = this;

    const helperFunction = (function() {
      console.log(self === obj);  // => true (self 指的是外部的 this 值)
      console.log(this === obj);  // => false (this 指的是全局对象。在严格模式下,它的值为undefined)
    })();
  }
};

obj.myMethod();

frames

在浏览器环境中访问全局对象的另一种方法是使用frames属性,其工作方式类似于selfwindow:

// browser environment
console.log(frames);    // => Window {...}

此只读属性通常用于获取当前窗口的子帧列表。例如,我们可以使用window.frames[0]frames[0]来访问第一帧。

global

Node.js中,可以使用global关键字访问全局对象:

// node 环境
console.log(global);    // => Object [global] {...}

windowselfframes 在 Node 环境中不起作用。请记住Node.js中的顶级作用域不是全局作用域。在浏览器中,var abc = 123将创建一个全局变量。 但是,在 Node.js 中,变量将是模块本身的局部变量。

this

在浏览器中,可以在程序的顶层使用this关键字来引用全局对象:

this.foo = 123;
console.log(this.foo === window.foo);    // => true

在非严格模式下运行的内部函数或箭头函数中的 this 也引用了全局对象。 但是,在严格模式下运行的函数不是这种情况,在这种模式下,this值为undefined

(function() {
  console.log(this);    // => Window {...}
})();

(() => {
  console.log(this);    // => Window {...}
})();

(function() {
  "use strict";
  console.log(this);    // => undefined
})();

Node 模块中,this在顶层不引用全局对象。相反,它具有与module.exports相同的值。在函数(Node 环境)内部,this值是根据调用函数的方式确定的。在 JS 模块中,this 在顶层是undefined的。

globalThis 的引入

globalThis旨在通过定义一个标准的全局属性来整合日益分散的访问全局对象的方法。该提案目前处于第四阶段,这意味着它已经准备好被纳入ES2020标准。所有流行的浏览器,包括Chrome 71+、Firefox 65+和Safari 12.1+,都已经支持这项功能。你也可以在Node.js 12+中使用它。

// 浏览器环境
console.log(globalThis);    // => Window {...}

// node.js 环境
console.log(globalThis);    // => Object [global] {...}

// web worker 环境
console.log(globalThis);    // => DedicatedWorkerGlobalScope {...}

通过使用globalThis,你的代码将在 window 和非 window 上下文中工作,而无需编写额外的检查或测试。在大多数环境中,globalThis直接引用该环境的全局对象。但是,在浏览器中,内部使用代理来考虑iframe和跨 window 安全性。 实际上,它并不会改变我们编写代码的方式。

另一方面,如果你确定你的代码将在什么环境中使用,那么可以使用现有的方法来引用环境的全局对象,这样就不必为globalThis包含一个polyfill了。

创建一个 globalThis polyfill

在引入globalThis之前,跨不同环境访问全局对象的常用方法是使用以下模式

function getGlobalObject() {
  return Function('return this')();
}

if (typeof getGlobalObject().Promise.allSettled !== 'function') {
  // the Promise.allSettled() method is not available in this environment
}


此代码的问题在于,在使用内容安全策略(CSP)的网站中不能使用Function构造函数和eval。 由于CSP,Chrome 的扩展程序系统也不允许此类代码运行。

引用全局对象的另一种模式如下:

function getGlobalObject() {
  if (typeof globalThis !== 'undefined') { return globalThis; }
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('cannot find the global object');
};

if (typeof getGlobalObject().Promise.allSettled !== 'function') {
  // the Promise.allSettled() method is not available in this environment
}

此模式通常在网络上使用。 但这也有一些缺陷,使其在某些情况下不可靠。 幸运的是,Chrome开发者工具(Chrome DevTools)的Mathias Bynens 提出了一种不受这些缺点影响的创造性模式:

(function() {
  if (typeof globalThis === 'object') return;
  Object.defineProperty(Object.prototype, '__magic__', {
    get: function() {
      return this;
    },
    configurable: true // This makes it possible to `delete` the getter later.
  });
  __magic__.globalThis = __magic__; // lolwat
  delete Object.prototype.__magic__;
}());

// Your code can use `globalThis` now.
console.log(globalThis);

与其他方法相比,这种 polyfill 是更可靠的解决方案,但仍然不够完美。 如 Mathias 所述,修改ObjectObject.definePropertyObject.prototype .__ defineGetter__可能会破坏 polyfill。

总结

编写可在多种环境下工作的可移植 JS 代码是很困难的。每个宿主环境都有稍微不同的对象模型。因此,要访问全局对象,需要在不同的 JS 环境中使用不同的语法。

随着globalThis属性的引入,访问全局对象将变得更加简单,并且不再需要检测代码运行的环境。

乍一看,globalThis 的实现很容易,但是在实践中,却是很复杂的。所有现有的实现方案都是不完美的,如果不小心的话可能会引入 bug。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://news.ycombinator.com/...


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

赞 4 收藏 2 评论 0

Fundebug 发布了文章 · 2020-02-10

Fundebug支持企业微信配置机器人报警

摘要: 为了帮助客户方便、及时地发现问题,Fundebug 支持企业微信报警了!

Fundebug是全栈 JavaScript 错误监控平台,支持前端 JavaScript, 后端 Node.js 以及微信小程序等应用监控。
如果配置了企业微信机器人,一旦应用出现错误,Fundebug 会及时发送报警信息到企业微信群。如下图所示:

如何添加企业微信机器人?

项目设置页面,切换到报警方式选项卡,在第三方报警中即可添加企业微信报警。

具体细节请参考接入文档:企业微信报警接入文档

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了30亿+错误事件,付费客户有阳光保险、达令家、核桃编程、荔枝FM、微脉等众多品牌企业。

查看原文

赞 1 收藏 0 评论 0

Fundebug 赞了文章 · 2019-12-18

回到基础:什么是DOM及DOM操作?

译者:前端小智
来源:valentinog

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

文本主要介绍文档对象模型(DOM),了解什么是DOM操作,以及如何使用砶 DOM API 与 JS 中的 Web 页面进行交互。

什么是 DOM ?

DOM(文档对象模型)是针对于xml但是扩展用于HTML的应用程序编程接口,定义了访问和操作HTML的文档的标准。

W3C文档对象模型是中立于平台和语言之间的接口,它允许程序和脚本动态的访问和更新文档的内容、结构、样式。总之HTML是关于如何获取、修改、添加和删除HTML元素的标准。

DOM 分层节点

DOM的分层节点一般被称作是DOM树,树中的所有节点都可以通过脚本语言例如JS进行访问,所有HTMlL元素节点都可以被创建、添加或者删除。

在DOM分层节点中,页面就是用分层节点图表示的。

  • 整个文档是一个文档节点,就想是树的根一样。
  • 每个HTML元素都是元素节点。
  • HTML元素内的文本就是文本节点。
  • 每个HTML属性时属性节点。

当咱们访问一个web页面时,浏览器会解析每个HTML元素,创建了HTML文档的虚拟结构,并将其保存在内存中。接着,HTML页面被转换成树状结构,每个HTML元素成为一个叶子节点,连接到父分支。
考虑以下 Html 结构:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>A super simple title!</title>
</head>
<body>
<h1>A super simple web page!</h1>
</body>
</html>

在这个结构的顶部有一个document,也称为根元素,它包含另一个元素:htmlhtml元素包含一个head,而 head 又有一个title。 然后body 包含一个h1。 每个HTML元素都由特定类型(也称为接口)表示,并且可能包含文本或其他嵌套元素:

document (HTMLDocument)
  |
  | --> html (HTMLHtmlElement)
          |  
          | --> head (HtmlHeadElement)
          |       |
          |       | --> title (HtmlTitleElement)
          |                | --> text: "A super simple title!"
          |
          | --> body (HtmlBodyElement)
          |       |
          |       | --> h1 (HTMLHeadingElement)
          |              | --> text: "A super simple web page!"

每个HTML元素都来自Element,但其中很大一部分都是专用的。 咱们可以检查原型以查找元素所属的“种类”。 例如,h1元素是HTMLHeadingElement

document.querySelector('h1').__proto__

// Output: HTMLHeadingElement

HTMLHeadingElement则是HTMLElement的后代:

document.querySelector('h1').__proto__.__proto__

// Output: HTMLElement

此时(特别是初学者)可能会对documentwindow之间的区别产生一些混淆。接下来看看它们有何不同!

document 和 window 之间的区别

简单来说,documentwindow的一个对象属性。window 对象表示浏览器中打开的窗口。如果文档包含框架(frameiframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。所有的全局函数和对象都属于 window 对象的属性和方法。

区别:

  1. window 指窗体。document指页面。documentwindow的一个子对象。
  2. 用户不能改变 document.location(因为这是当前显示文档的位置)。但是,可以改变window.location (用其它文档取代当前文档)window.location本身也是一个对象,而document.location不是对象。

document接口有许多实用方法,比如querySelector(),它是用于查找给定页面内HTML元素的方法:

document.querySelector('h1');

window表示当前的浏览器,下面代码与上面等价:

window.document.querySelector('h1');

当然,更常见的是用第一种方式。

window是一个全局对象,可以从浏览器中运行的任何JS代码直接访问。 window暴露了很多属性和方法,如:

window.alert('Hello world'); // Shows an alert
window.setTimeout(callback, 3000); // Delay execution
window.fetch(someUrl); // make XHR requests
window.open(); // Opens a new tab
window.location; // Browser location
window.history; // Browser history
window.navigator; // The actual user agent
window.document; // The current page

因为这些属性和方法也是全局的,所以也可以这样访问它们

alert('Hello world'); // Shows an alert
setTimeout(callback, 3000); // Delay execution
fetch(someUrl); // make XHR requests
open(); // Opens a new tab
location; // Browser location
history; // Browser history
navigator; // The actual user agent
document;// The current page

其中有些咱们都已经很熟悉了,如setTimeout() 的方法。 例如,当咱们想要得知当前用户的浏览器语言时,window.navigator就非常有用:

if (window.navigator) {
  var lang = window.navigator.language;
  if (lang === "en-US") {
    // show something
  }

  if (lang === "it-IT") {
    // show something else
  }
}

DOM 常用方法

获取节点

// 通过id号来获取元素,返回一个元素对象
document.getElementById(idName) 
      
// 通过name属性获取id号,返回元素对象数组 
document.getElementsByName(name)  
   
// 通过class来获取元素,返回元素对象数组
document.getElementsByClassName(className)   

// 通过标签名获取元素,返回元素对象数组
document.getElementsByTagName(tagName)       

获取/设置元素的属性值:


// 括号传入属性名,返回对应属性的属性值
element.getAttribute(attributeName)

// 传入属性名及设置的值
element.setAttribute(attributeName,attributeValue)

创建节点Node

// 创建一个html元素,这里以创建h3元素为例
document.createElement("h3")

// 创建一个文本节点;
document.createTextNode(String);

// 创建一个属性节点,这里以创建class属性为例
document.createAttribute("class");

增添节点

// 往element内部最后面添加一个节点,参数是节点类型
element.appendChild(Node);

// 在element内部的中在existingNode前面插入newNode
elelment.insertBefore(newNode,existingNode); 

删除节点

//删除当前节点下指定的子节点,删除成功返回该被删除的节点,否则返回null
element.removeChild(Node)

DOM常用属性

获取当前元素的父节点

// 返回当前元素的父节点对象
element.parentNode

获取当前元素的子节点

// 返回当前元素所有子元素节点对象,只返回HTML节点
element.chlidren

// 返回当前元素多有子节点,包括文本,HTML,属性节点。(回车也会当做一个节点)
element.chilidNodes

// 返回当前元素的第一个子节点对象
element.firstChild

// 返回当前元素的最后一个子节点对象
element.lastChild

获取当前元素的同级元素

// 返回当前元素的下一个同级元素 没有就返回null
element.nextSibling

// 返回当前元素上一个同级元素 没有就返回 null
element.previousSibling

获取当前元素的文本

// 返回元素的所有文本,包括html代码
element.innerHTML

// 返回当前元素的自身及子代所有文本值,只是文本内容,不包括html代码
element.innerText

获取当前节点的节点类型

// 返回节点的类型,数字形式(1-12)
// 常见几个1:元素节点,2:属性节点,3:文本节点。
node.nodeType   

设置样式

// 设置元素的样式时使用style
element.style.color=“#eea”;

DOM 操作

DOM中的每个HTML元素也是一个节点,可以像这样查找节点:

document.querySelector('h1').nodeType;

上面会返回1,它是Element类型的节点的标识符,还可以检查节点名称:

document.querySelector('h1').nodeName;

"H1"

上面的示例返回大写的节点名。但是需要理解的最重要的概念是,咱们主要使用DOM中的两种类型的节点:

  • 元素节点
  • 文本节点

创建元素节点,可以通过 createElement方法:

var heading = document.createElement('h1');

创建文本节点,可能通过 createTextNode 方法:

var text = document.createTextNode('Hello world');

接着将两个节点组合在一起,然后添加到 body 上:

var heading = document.createElement('h1');
var text = document.createTextNoe('Hello world');
heading.appendChild(text);
document.body.appendChild(heading)

在学习Dom操作时候,这些方法需要牢记并熟练使用的。

目前像咱们用这种方式创建和操作元素,是属于命令式DOM操作。现代前端库通过支持声明性方法来解决这个问题,如 JQuery,咱们可以声明需要什么HTML元素,其它就由库来完成。

DOM 操作和 jQuery

大部分可能会想,咱们直接使用 JQ 不就行了,为啥还要用如createElement这些原生的方法,多费劲。

请注意jQuery正在渐渐消失。Bootstrap 5将把它从依赖项中删除,还有很多项目也在删除它。这背后有一个合理的原因:原生DOM API提供了大量像JQ这样操作DOM的简便方法,足以替代jQuery一些常用的DOM操作。

如果只是想进行简单的交互和操作,请使用普通的JS。咱们甚至可以创建自己的迷你框架来抽象最常见的操作:创建元素、追加、创建文本。

总结

DOM是浏览器创建并保留在内存中的网页的虚拟副本。创建、修改、删除 HTML 元素,这些属于 “DOM 操作”。在过去即使对于更简单的任务,咱们也要依赖于 jQuery,但今天原生 API 已经互相兼容并且足够成熟足以替代 jQuery 了。

jQuery不会很快消失,但是每个JS开发人员都必须知道如何使用原生API操作DOM。这样做有很多原因,额外的库增加了JS应用程序的加载时间和大小,更不用说DOM操作在技术面试也经常出现。

操作 DOM 最常用的方法是 document.createElement() 用于创建新的 HTML 元素,document.createTextNode() 用于在 DOM 内创建文本节点。需要注意的是 .appendChild() 用于将新的 HTML 元素或文本节点附加到现有元素。

虽然很好的了解本机 API 是很好的,但是现代前端库也提供了无可置疑的好处。尽管用“原生” JS 去构建大型JS 程序确实是可行的,但有时 Angular、React、Vue可以提供很多帮助。仅使用 JavaScript 来处理更简单的原型和中小型应用也是明智之举。

原文:https://www.valentinog.com/bl...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 17 收藏 11 评论 0

Fundebug 赞了文章 · 2019-12-18

34 个今年11月最受欢迎的 JavaScript 库

作者:Iren Korkishko
译者:前端小智
来源:dev

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

直接开门见山,看看有哪些好用受欢迎的库值得我们使用。

1.nodemon

GitHubhttps://github.com/remy/nodemon

GitHub Stars: 19.6 k

在编写调试Node.js项目,修改代码后,需要频繁的手动close掉,然后再重新启动,非常繁琐。现在,我们可以使用nodemon这个工具,它的作用是监听代码文件的变动,当代码改变之后,自动重启。

2.Sharp

网址:https://sharp.pixelplumbing.c...

GitHubhttps://github.com/lovell/sharp

GitHub Stars: 15 k

sharp 是 Node.js 平台上相当热门的一个图像处理库,其实际上是基于 C 语言编写 的 libvips 库封装而来,因此高性能也成了 sharp 的一大卖点。sharp 可以方便地实现常见的图片编辑操作,如裁剪、格式转换、旋转变换、滤镜添加等。

3.Fabric.js

网址:http://fabricjs.com/

GitHubhttps://github.com/fabricjs/f...

GitHub Stars: 14.6 k

Fabric 是一个强大而简单的 JS Canvas 库,我们能通过使用它实现在 Canvas 上创建、填充图形、给图形填充渐变颜色。 组合图形(包括组合图形、图形文字、图片等)等一系列功能。简单来说我们可以通过使用 Fabric 从而以较为简单的方式实现较为复杂的 Canvas 功能

4.Immer

网址:https://immerjs.github.io/imm...

GitHubhttps://github.com/immerjs/immer

GitHub Stars: 14.3 k

Immermobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,解决了许多日常开发中的棘手问题。

5.Mark Text

网址:https://marktext.app/

GitHubhttps://github.com/marktext/m...

GitHub Stars: 14 k

Mark Text 所输及所见,摒弃了众多 markdown 编辑器左边写作右边预览的写作方式,巧妙的将编辑和预览融为一体。

6.React Testing Library

网址:https://testing-library.com/r...

GitHubhttps://github.com/testing-li...

GitHub Stars: 9.9 k

简单而完整的React DOM测试实用程序,鼓励良好的测试实践。

7.PostGraphile

文档:https://www.graphile.org/post...

GitHubhttps://github.com/graphile/p...

GitHub Stars: 8.2 k

PostGraphile 是即时GraphQL APIPostGraphile自动检测表、列、索引、关系、视图、类型、函数、注释等。

8.Botpress

网站地址:https://botpress.io/

GitHubhttps://github.com/botpress/b...

GitHub Stars: 8.2 k

一个用Javascript编写开源bot创建工具,它由社区建立的一组丰富源模块提供支持。 我们喜欢说Botpress 就像ChatbotsWordpress; 任何人都可以创建和重用其他人的模块。

9.downshift

网站地址:https://downshift.netlify.com/

GitHubhttps://github.com/downshift-...

GitHub Stars: 7.7 k

downshift 用于构建简单,灵活,符合 WAI-ARIA 标准的增强型输入React组件的原语

10. NG Bootstrap

网站地址:https://ng-bootstrap.github.i...

GitHubhttps://github.com/ng-bootstr...

GitHub Stars: 7.3 k

NG Bootstrap 是基于 Angular 开发的 Bootstrap CSS 框架的指令集,它是专为 Bootstrap 4 开发的 Angular 组件,由 ui-bootstrap 团队构建。

11.Ajv

网站地址:https://ajv.js.org/

GitHubhttps://github.com/epoberezki...

GitHub Stars: 6.7 k

AJV是一个基于JSON-Schema的依赖包,他可以将我们定义的Schema格式作为参数生成一个对象,使用这个对象的构造函数可以用于检测数据的合法性,除此之外还能够自定义directive来产生我们需要的format

12.Wiki.js

网站地址:https://wiki.js.org/

GitHubhttps://github.com/Requarks/wiki

GitHub Stars: 6 k

Wiki.js是一个基于Node.jsGitMarkdown构建的免费开源,现代且功能强大的wiki应用程序。支持用户登录,可以让用户在本地注册或直接使用MicrosoftGoogleGitHub等账户无缝登录体验,并选择限制对特定用户或甚至部分内容的Wiki访问。还可以定时远程备份数据到Git远程仓库。支持WindowsLinuxMac平台,这里就说下Linux系统手动安装教程。

13.Ky

GitHubhttps://github.com/sindresorh...

GitHub Stars: 5 k

Ky 是一个基于浏览器Fetch API的简洁优雅HTTP客户端。

14.tui.chart

网站地址:https://ui.toast.com/tui-chart/

GitHubhttps://github.com/nhn/tui.chart

GitHub Stars: 4.4 k

tui.chart是一个直观且易于应用漂亮的数据可视化图表。一个开源JavaScript库,
可以在包括IE8在内的传统浏览器中使用,使用许多选项和自定义主题更详细地更改图表。

15.Duktape

GitHubhttps://github.com/svaarala/d...

GitHub Stars: 4.2 k

Duktape 是一个轻量级的嵌入式 JavaScript 引擎,专注于可移植性和低占用率。

Duktape 可以被轻松地集成进一个 C/C++ 项目中:只需要将 duktape.cduktape.hduk_config.h 三个文件加入你的构建项目中,并使用 Duktape API 来实现 C 代码与 ECMAScript 函数的双向调用。

16.vue-interactive-paycard

事例:https://codepen.io/JavaScript...

GitHubhttps://github.com/muhammeder...

GitHub Stars: 4 k

vue-interactive-paycard是一种信用卡形式,具有流畅而微互动功能。 包括数字格式,验证和自动卡类型检测,使用Vue.js构建,并且具有充分的响应能力。

17.node-qrcode

GitHubhttps://github.com/soldair/no...

GitHub Stars: 3.5 k

node-qrcode是一个QR码/2d条码生成器。

18.WebGLStudio.js

GitHubhttps://github.com/jagenjo/we...

GitHub Stars: 3.6 k

WebGLStudio.js是一个基于浏览器的开源3D图形套件。可以使用标准Web技术在浏览器中编辑场景和材质,设计效果和着色器。

一些重要的WebGLStudio.js功能:

  • 完整的3D图形引擎(LiteScene.js),支持多个灯光,阴影贴图,实时反射,自定义材质,postFX,蒙皮,动画等等。
  • 一个易于使用,你所看到的是你得到的(WYSIWYG)编辑器,它为所有编码,图形合成和时间线功能提供单一界面。
  • 用于控制行为和后处理效果的图形编辑器。
  • 支持LiteFileSystem.js,这是一个虚拟文件系统,允许在Web上拖放存储资源,具有可配置的配额,用户和共享文件夹。
  • 通过发送单个链接导出和共享您的工作。

19.vue-chartjs

网站地址:https://vue-chartjs.org/

GitHubhttps://github.com/aperturele...

GitHub Stars: 3.4 k

vue-chartjs 让你在 Vue 中能更好的使用 Chart.js 。非常适合想要尽快启动和运行简单图表的人,它抽象了一些简单的逻辑, 但是也暴露了 Chart.js 对象, 提供了极大的灵活性。

20.ScrollTrigger

网站地址:https://terwanerik.github.io/...

GitHubhttps://github.com/terwanerik...

GitHub Stars: 3.1 k

ScrollTrigger用于根据当前滚动位置触发类。 例如。 当元素进入视口时,将其淡入。可以为每个元素添加自定义偏移量,或在视口上设置偏移量(例如,始终在元素达到视口的20%之后触发)。

21.Geolib

GitHubhttps://github.com/manuelbieh...

GitHub Stars: 2.9 k

Geolib是一个提供基本地理空间操作的库,例如距离计算,将十进制坐标转换为六进制,反之亦然等,此库当前为2D。

22.progress-estimator

GitHubhttps://github.com/bvaughn/pr...

GitHub Stars: 1.9 k

progress-estimator:记录进度条并估算Promise完成所需的时间

23.node-oracledb

网站地址:http://oracle.github.io/node-...

GitHubhttps://github.com/oracle/nod...

GitHub Stars: 1.7 k

node-oracledb 驱动程序连接到 Oracle 数据库,提高应用的速度、丰富应用的功能。它是一个具有 Apache 2.0 许可的开源项目。它由 Oracle 维护,仍在不断发展中。

24.node-mssql

网站地址:https://tediousjs.github.io/n...

GitHubhttps://github.com/tediousjs/...

GitHub Stars: 1.6 k

node-mssql是一个针对Node.jsMicrosoft SQL Server客户端。

25. Proton

网站地址:https://a-jie.github.io/Proton/

GitHubhttps://github.com/a-jie/Proton

GitHub Stars: 1.5 k

Proton是一个轻量级的 Javascript 粒子动画库。使用它可以很容易地创建各种各样的酷粒子效果。

26.litegraph.js

GitHubhttps://github.com/jagenjo/li...

GitHub Stars: 1.3 k

使用Javascript编写的图形节点引擎和编辑器。

27.Ackee

网站地址:https://ackee.electerious.com/

GitHubhttps://github.com/electeriou...

GitHub Stars: 1.2 k

Ackee是一个自托管的,基于Node.js的分析工具,主要服务那些关心隐私的人。Ackee在我们自己的服务器上运行,分析我们的网站流量,并在一个最小的界面中提供有用的统计数据。

28.SiriWave

GitHubhttps://github.com/kopiro/sir...

GitHub Stars: 949

SiriWave是使用Canvas API以纯 JS 复制的 “Apple Siri”波形。

29.VSCode Glean

VSCode Glean是一个扩展,它为你的React/Javascript/Typescript代码库提供重构工具。

GitHubhttps://github.com/wix/vscode...

GitHub Stars: 868

VSCode Glean 是一个扩展,可以为React/Javascript/Typescript代码库提供重构工具

图片描述

30.react-inlinesvg

事例:https://codesandbox.io/s/j25bv

GitHubhttps://github.com/gilbarbara...

GitHub Stars: 802

react-inlinesvg是一个用于ReactJS的SVG加载器组件

31.elm-live

网站地址:https://www.elm-live.com/

GitHubhttps://github.com/wking-io/e...

GitHub Stars: 795

elm-liveElm的一个灵活的开发服务器

32.foxr

GitHubhttps://github.com/deepsweet/...

GitHub Stars: 750

foxr 类似Puppeteer用于控制Firefox的Node.js API。

33.cockatiel

GitHubhttps://github.com/deepsweet/...

GitHub Stars: 746

Cockatiel是一个弹性和瞬态故障处理库,如重试,断路器,超时,隔板隔离和回退之类的策略。

34. Transloco

网站地址:https://transloco.netlify.com/

GitHubhttps://github.com/ngneat/tra...

GitHub Stars: 594

TranslocoAngular的国际化(i18n)库。 它允许我们为内容定义不同语言的翻译,并在运行时轻松地在它们之间切换。


原文:https://dev.to/iriskatastic/3...

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 55 收藏 42 评论 0

Fundebug 赞了文章 · 2019-12-16

百图详解红黑树,想不理解都难

之前在公司组内分享了红黑树的工作原理,今天把它整理下发出来,希望能对大家有所帮助,对自己也算是一个知识点的总结。

这篇文章算是我写博客写公众号以来画图最多的一篇文章了,没有之一,我希望尽可能多地用图片来形象地描述红黑树的各种操作的前后变换原理,帮助大家来理解红黑树的工作原理,下面,多图预警开始了。

在讲红黑树之前,我们首先来了解下下面几个概念:二叉树,排序二叉树以及平衡二叉树。

二叉树

二叉树指的是每个节点最多只能有两个字数的有序树。通常左边的子树称为左子树 ,右边的子树称为右子树 。这里说的有序树强调的是二叉树的左子树和右子树的次序不能随意颠倒。

二叉树简单的示意图如下:

代码定义:

class Node {
    T data;
    Node left;
    Node right;
}
复制代码

排序二叉树

所谓排序二叉树,顾名思义,排序二叉树是有顺序的,它是一种特殊结构的二叉树,我们可以对树中所有节点进行排序和检索。

性质
  • 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若她的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 具有递归性,排序二叉树的左子树、右子树也是排序二叉树。

排序二叉树简单示意图:

排序二叉树

排序二叉树退化成链表

排序二叉树的左子树上所有节点的值小于根节点的值,右子树上所有节点的值大于根节点的值,当我们插入一组元素正好是有序的时候,这时会让排序二叉树退化成链表。

正常情况下,排序二叉树是如下图这样的:

但是,当插入的一组元素正好是有序的时候,排序二叉树就变成了下边这样了,就变成了普通的链表结构,如下图所示:

正常情况下的排序二叉树检索效率类似于二分查找,二分查找的时间复杂度为 O(log n),但是如果排序二叉树退化成链表结构,那么检索效率就变成了线性的 O(n) 的,这样相对于 O(log n) 来说,检索效率肯定是要差不少的。

思考,二分查找和正常的排序二叉树的时间复杂度都是 O(log n),那么为什么是O(log n) ?

关于 O(log n) 的分析下面这篇文章讲解的非常好,感兴趣的可以看下这篇文章 二分查找的时间复杂度.md),文章是拿二分查找来举例的,二分查找和平衡二叉树的时间复杂度是一样的,理解了二分查找的时间复杂度,再来理解平衡二叉树就不难了,这里就不赘述了。

继续回到我们的主题上,为了解决排序二叉树在特殊情况下会退化成链表的问题(链表的检索效率是 O(n) 相对正常二叉树来说要差不少),所以有人发明了平衡二叉树红黑树类似的平衡树。

平衡二叉树

平衡二叉数又被称为 AVL 树,AVL 树的名字来源于它的发明作者 G.M. Adelson-Velsky 和 E.M. Landis,取自两人名字的首字母。

官方定义:它或者是一颗空树,或者具有以下性质的排序二叉树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。

两个条件:

  • 平衡二叉树必须是排序二叉树,也就是说平衡二叉树他的左子树所有节点的值必须小于根节点的值,它的右子树上所有节点的值必须大于它的根节点的值。
  • 左子树和右子树的深度之差的绝对值不超过1。

红黑树

讲了这么多概念,接下来主角红黑树终于要上场了。

为什么有红黑树?

其实红黑树和上面的平衡二叉树类似,本质上都是为了解决排序二叉树在极端情况下退化成链表导致检索效率大大降低的问题,红黑树最早是由 Rudolf Bayer 于 1972 年发明的。

红黑树首先肯定是一个排序二叉树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是 RED 或 BLACK 。

Java 中实现红黑树大概结构图如下所示:

红黑树的特性

  • 性质1:每个节点要么是红色,要么是黑色。
  • 性质2:根节点永远是黑色的。
  • 性质3:所有的叶子节点都是空节点(即null),并且是黑色的。
  • 性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点。)
  • 性质5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

针对上面的 5 种性质,我们简单理解下,对于性质 1 和性质 2 ,相当于是对红黑树每个节点的约束,根节点是黑色,其他的节点要么是红色,要么是黑色。

对于性质 3 中指定红黑树的每个叶子节点都是空节点,而且叶子节点都是黑色,但 Java 实现的红黑树会使用 null 来代表空节点,因此我们在遍历 Java里的红黑树的时候会看不到叶子节点,而看到的是每个叶子节点都是红色的,这一点需要注意。

对于性质 5,这里我们需要注意的是,这里的描述是从任一节点,从任一节点到它的子树的每个叶子节点黑色节点的数量都是相同的,这个数量被称为这个节点的黑高。

如果我们从根节点出发到每个叶子节点的路径都包含相同数量的黑色节点,这个黑色节点的数量被称为树的黑色高度。树的黑色高度和节点的黑色高度是不一样的,这里要注意区分。

其实到这里有人可能会问了,红黑树的性质说了一大堆,那是不是说只要保证红黑树的节点是红黑交替就能保证树是平衡的呢?

其实不是这样的,我们可以看来看下面这张图:

左边的子树都是黑色节点,但是这个红黑树依然是平衡的,5 条性质它都满足。

这个树的黑色高度为 3,从根节点到叶子节点的最短路径长度是 2,该路径上全是黑色节点,包括叶子节点,从根节点到叶子节点最长路径为 4,每个黑色节点之间会插入红色节点。

通过上面的性质 4 和性质 5,其实上保证了没有任何一条路径会比其他路径长出两倍,所以这样的红黑树是平衡的。

其实这算是一个推论,红黑树在最差情况下,最长的路径都不会比最短的路径长出两倍。其实红黑树并不是真正的平衡二叉树,它只能保证大致是平衡的,因为红黑树的高度不会无限增高,在实际应用用,红黑树的统计性能要高于平衡二叉树,但极端性能略差。

红黑树的插入

想要彻底理解红黑树,除了上面说到的理解红黑树的性质以外,就是理解红黑树的插入操作了。

红黑树的插入和普通排序二叉树的插入基本一致,排序二叉树的要求是左子树上的所有节点都要比根节点小,右子树上的所有节点都要比跟节点大,当插入一个新的节点的时候,首先要找到当前要插入的节点适合放在排序二叉树哪个位置,然后插入当前节点即可。红黑树和排序二叉树不同的是,红黑树需要在插入节点调整树的结构来让树保持平衡。

一般情况下,红黑树中新插入的节点都是红色的,那么,为什么说新加入到红黑树中的节点要是红色的呢?

这个问题可以这样理解,我们从性质5中知道,当前红黑树中从根节点到每个叶子节点的黑色节点数量是一样的,此时假如新的黑色节点的话,必然破坏规则,但加入红色节点却不一定,除非其父节点就是红色节点,因此加入红色节点,破坏规则的可能性小一些。

接下来我们重点来讲红黑树插入新节点后是如何保持平衡的。

给定下面这样一颗红黑树:

当我们插入值为66的节点的时候,示意图如下:

很明显,这个时候结构依然遵循着上述5大特性,无需启动自动平衡机制调整节点平衡状态。

如果再向里面插入值为51的节点呢,这个时候红黑树变成了这样。

这样的结构实际上是不满足性质4的,红色两个子节点必须是黑色的,而这里49这个红色节点现在有个51的红色节点与其相连。

这个时候我们需要调整这个树的结构来保证红黑树的平衡。

首先尝试将49这个节点设置为黑色,如下示意图。

这个时候我们发现黑高是不对的,其中 60-56-45-49-51-null 这条路径有 4 个黑节点,其他路径的黑色节点是 3 个。

接着调整红黑树,我们再次尝试把45这个节点设置为红色的,如下图所示:

这个时候我们发现问题又来了,56-45-43 都是红色节点的,出现了红色节点相连的问题。

于是我们需要再把 56 和 43 设置为黑色的,如下图所示。

于是我们把 68 这个红色节点设置为黑色的。

对于这种红黑树插入节点的情况下,我们可以只需要通过变色就可以保持树的平衡了。但是并不是每次都是这么幸运的,当变色行不通的时候,我们需要考虑另一个手段就是旋转了。

例如下面这种情况,同样还是拿这颗红黑树举例。

现在这颗红黑树,我们现在插入节点65。

我们尝试把 66 这个节点设置为黑色,如下图所示。

这样操作之后黑高又出现不一致的情况了,60-68-64-null 有 3 个黑色节点,而60-68-64-66-null 这条路径有 4 个黑色节点,这样的结构是不平衡的。

或者我们把 68 设置为黑色,把 64 设置为红色,如下图所示:

但是,同样的问题,上面这颗红黑树的黑色高度还是不一致,60-68-64-null 和 60-68-64-66-null 这两条路径黑色高度还是不一致。

这种情况如果只通过变色的情况是不能保持红黑树的平衡的。

红黑树的旋转

接下来我们讲讲红黑树的旋转,旋转分为左旋和右旋。

左旋

文字描述:逆时针旋转两个节点,让一个节点被其右子节点取代,而该节点成为右子节点的左子节点。

文字描述太抽象,接下来看下图片展示。

首先断开节点PL与右子节点G的关系,同时将其右子节点的引用指向节点C2;然后断开节点G与左子节点C2的关系,同时将G的左子节点的应用指向节点PL。

接下来再放下 gif 图,希望能帮助大家更好地理解左旋,图片来自网络。

右旋

文字描述:顺时针旋转两个节点,让一个节点被其左子节点取代,而该节点成为左子节点的右子节点。

右旋的图片展示:

首先断开节点G与左子节点PL的关系,同时将其左子节点的引用指向节点C2;然后断开节点PL与右子节点C2的关系,同时将PL的右子节点的应用指向节点G。

右旋的gif展示(图片来自网络):

介绍完了左旋和右旋基本操作,我们来详细介绍下红黑树的几种旋转场景。

左左节点旋转(插入节点的父节点是左节点,插入节点也是左节点)

如下图所示的红黑树,我们插入节点是65。

操作步骤如下可以围绕祖父节点 69 右旋,再结合变色,步骤如下所示:

左右节点旋转(插入节点的父节点是左节点,插入节点是右节点)

还是上面这颗红黑树,我们再插入节点 67。

这种情况我们可以这样操作,先围绕父节点 66 左旋,然后再围绕祖父节点 69 右旋,最后再将 67 设置为黑色,把 69 设置为红色,如下图所示。

右左节点旋转(插入节点的父节点是右节点,插入节点左节点)

如下图这种情况,我们要插入节点68。

这种情况,我们可以先围绕父节点 69 右旋,接着再围绕祖父节点 66 左旋,最后把 68 节点设置为黑色,把 66 设置为红色,我们的具体操作步骤如下所示。

右右节点旋转(插入节点的父节点是右节点,插入节点也是右节点)

还是来上面的图来举例,我们在这颗红黑树上插入节点 70 。

我们可以这样操作围绕祖父节点 66 左旋,再把旋转后的根节点 69 设置为黑色,把 66 这个节点设置为红色。具体可以参看下图:

红黑树在 Java 中的实现

Java 中的红黑树实现类是 TreeMap ,接下来我们尝试从源码角度来逐行解释 TreeMap 这一套机制是如何运作的。

// TreeMap中使用Entry来描述每个节点
 static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
        ...
 }
复制代码

TreeMap 的put方法。

 public V put(K key, V value) {
        //先以t保存链表的root节点
        Entry<K,V> t = root;
        //如果t=null,表明是一个空链表,即该TreeMap里没有任何Entry作为root
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            //将新的key-value创建一个Entry,并将该Entry作为root
            root = new Entry<>(key, value, null);
            size = 1;
            //记录修改次数加1
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        //如果比较器cpr不为null,即表明采用定制排序
        if (cpr != null) {
            do {
                //使用parent上次循环后的t所引用的Entry
                parent = t;
                 //将新插入的key和t的key进行比较
                cmp = cpr.compare(key, t.key);
                //如果新插入的key小于t的key,t等于t的左边节点
                if (cmp < 0)
                    t = t.left;
                //如果新插入的key大于t的key,t等于t的右边节点    
                else if (cmp > 0)
                    t = t.right;
                else
                //如果两个key相等,新value覆盖原有的value,并返回原有的value
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        //将新插入的节点作为parent节点的子节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        //如果新插入key小于parent的key,则e作为parent的左子节点
        if (cmp < 0)
            parent.left = e;
        //如果新插入key小于parent的key,则e作为parent的右子节点
        else
            parent.right = e;
        //修复红黑树
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
复制代码
//插入节点后修复红黑树
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    //直到x节点的父节点不是根,且x的父节点是红色
    while (x != null && x != root && x.parent.color == RED) {
        //如果x的父节点是其父节点的左子节点
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            //获取x的父节点的兄弟节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            //如果x的父节点的兄弟节点是红色
            if (colorOf(y) == RED) {     
                //将x的父节点设置为黑色
                setColor(parentOf(x), BLACK);
                //将x的父节点的兄弟节点设置为黑色
                setColor(y, BLACK);
                //将x的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            }
            //如果x的父节点的兄弟节点是黑色
            else {   
                //TODO 对应情况第二种,左右节点旋转
                //如果x是其父节点的右子节点
                if (x == rightOf(parentOf(x))) {
                    //将x的父节点设为x
                    x = parentOf(x);
                    //右旋转
                    rotateLeft(x);
                }
                //把x的父节点设置为黑色
                setColor(parentOf(x), BLACK);
                //把x的父节点父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        }
        //如果x的父节点是其父节点的右子节点
        else {
            //获取x的父节点的兄弟节点
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            //只着色的情况对应的是最开始例子,没有旋转操作,但是要对应多次变换
            //如果x的父节点的兄弟节点是红色  
            if (colorOf(y) == RED) {
                //将x的父节点设置为黑色
                setColor(parentOf(x), BLACK);
                //将x的父节点的兄弟节点设为黑色
                setColor(y, BLACK);
                //将X的父节点的父节点(G)设置红色
                setColor(parentOf(parentOf(x)), RED);
                //将x设为x的父节点的节点
                x = parentOf(parentOf(x));
            }
            //如果x的父节点的兄弟节点是黑色
            else {
                //如果x是其父节点的左子节点
                if (x == leftOf(parentOf(x))) {
                    //将x的父节点设为x
                    x = parentOf(x);
                    //右旋转
                    rotateRight(x);
                }
                //将x的父节点设为黑色
                setColor(parentOf(x), BLACK);
                //把x的父节点的父节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    //将根节点强制设置为黑色
    root.color = BLACK;
}
复制代码

TreeMap的插入节点和普通的排序二叉树没啥区别,唯一不同的是,在TreeMap 插入节点后会调用方法fixAfterInsertion(e)来重新调整红黑树的结构来让红黑树保持平衡。

我们重点关注下红黑树的fixAfterInsertion(e)方法,接下来我们来分别介绍两种场景来演示fixAfterInsertion(e)方法的执行流程。

第一种场景:只需变色即可平衡

同样是拿这颗红黑树举例,现在我们插入节点 51。

当我们需要插入节点51的时候,这个时候TreeMap 的 put 方法执行后会得到下面这张图。

接着调用fixAfterInsertion(e)方法,如下代码流程所示。

当第一次进入循环后,执行后会得到下面的红黑树结构。

在把 x 重新赋值后,重新进入 while 循环,此时的 x 节点为 45 。

执行上述流程后,得到下面所示的红黑树结构。

这个时候x被重新赋值为60,因为60是根节点,所以会退出 while 循环。在退出循序后,会再次把根节点设置为黑色,得到最终的结构如下图所示。

最后经过两次执行while循环后,我们的红黑树会调整成现在这样的结构,这样的红黑树结构是平衡的,所以路径的黑高一致,并且没有红色节点相连的情况。

第二种场景 旋转搭配变色来保持平衡

接下来我们再来演示第二种场景,需要结合变色和旋转一起来保持平衡。

给定下面这样一颗红黑树:

现在我们插入节点66,得到如下树结构。

同样地,我们进入fixAfterInsertion(e)方法。

最终我们得到的红黑树结构如下图所示:

调整成这样的结构我们的红黑树又再次保持平衡了。

演示 TreeMap 的流程就拿这两种场景举例了,其他的就不一一举例了。

红黑树的删除

因为之前的分享只整理了红黑树的插入部分,本来想着红黑树的删除就不整理了,有人跟我反馈说红黑树的删除相对更复杂,于是索性还是把红黑树的删除再整理下。

删除相对插入来说,的确是要复杂一点,但是复杂的地方是因为在删除节点的这个操作情况有很多种,但是插入不一样,插入节点的时候实际上这个节点的位置是确定的,在节点插入成功后只需要调整红黑树的平衡就可以了。

但是删除不一样的是,删除节点的时候我们不能简单地把这个节点设置为null,因为如果这个节点有子节点的情况下,不能简单地把当前删除的节点设置为null,这个被删除的节点的位置需要有新的节点来填补。这样一来,需要分多种情况来处理了。

删除节点是根节点

直接删除根节点即可。

删掉节点的左子节点和右子节点都是为空

直接删除当前节点即可。

删除节点有一个子节点不为空

这个时候需要使用子节点来代替当前需要删除的节点,然后再把子节点删除即可。

给定下面这棵树,当我们需要删除节点69的时候。

首先用子节点代替当前待删除节点,然后再把子节点删除。

最终的红黑树结构如下面所示,这个结构的红黑树我们是不需要通过变色+旋转来保持红黑树的平衡了,因为将子节点删除后树已经是平衡的了。

还有一种场景是当我们待删除节点是黑色的,黑色的节点被删除后,树的黑高就会出现不一致的情况,这个时候就需要重新调整结构。

还是拿上面这颗删除节点后的红黑树举例,我们现在需要删除节点67。

因为67 这个节点的两个子节点都是null,所以直接删除,得到如下图所示结构:

这个时候我们树的黑高是不一致的,左边黑高是3,右边是2,所以我们需要把64节点设置为红色来保持平衡。

删除节点两个子节点都不为空

删除节点两个子节点都不为空的情况下,跟上面有一个节点不为空的情况下也是有点类似,同样是需要找能替代当前节点的节点,找到后,把能替代删除节点值复制过来,然后再把替代节点删除掉。

  • 先找到替代节点,也就是前驱节点或者后继节点
  • 然后把前驱节点或者后继节点复制到当前待删除节点的位置,然后在删除前驱节点或者后继节点。

那么什么叫做前驱,什么叫做后继呢? 前驱是左子树中最大的节点,后继则是右子树中最小的节点。

前驱或者后继都是最接近当前节点的节点,当我们需要删除当前节点的时候,也就是找到能替代当前节点的节点,能够替代当前节点肯定是最接近当前节点。

在当前删除节点两个子节点不为空的场景下,我们需要再进行细分,主要分为以下三种情况。

第一种,前驱节点为黑色节点,同时有一个非空节点

如下面这样一棵树,我们需要删除节点64:

首先找到前驱节点,把前驱节点复制到当前节点:

接着删除前驱节点。

这个时候63和60这个节点都是红色的,我们尝试把60这个节点设置为红色即可使整个红黑树达到平衡。

第二种,前驱节点为黑色节点,同时子节点都为空

前驱节点是黑色的,子节点都为空,这个时候操作步骤与上面基本类似。

如下操作步骤:

因为要删除节点64,接着找到前驱节点63,把63节点复制到当前位置,然后将前驱节点63删除掉,变色后出现黑高不一致的情况下,最后把63节点设置为黑色,把65节点设置为红色,这样就能保证红黑树的平衡。

第三种,前驱节点为红色节点,同时子节点都为空

给定下面这颗红黑树,我们需要删除节点64的时候。

同样地,我们找到64的前驱节点63,接着把63赋值到64这个位置。

然后删除前驱节点。

删除节点后不需要变色也不需要旋转即可保持树的平衡。

终于把红黑树的基本原理部分写完了,用了很多示意图,这篇文章是在之前分享的 ppt 上再整理出来,我觉得自己应该算是把基本操作讲明白了,整理这篇文章前前后后用了近一周左右,因为平时上班,基本上只有周末有时间才有时间整理,如有问题请留言讨论。

如果您觉得写得还可以,请您帮忙点个赞,您的点赞真的是对我最大的支持,也是我能继续写下去的动力,感谢。

转自:https://juejin.im/post/5df4aa...

更多精彩文章,关注公众号【ToBeTopJavaer】,更有如下数万元精品vip资源免费等你来拿!!!
1574903758(1).jpg.jpg")

1574903796(1).jpg.jpg")

查看原文

赞 5 收藏 4 评论 1

Fundebug 赞了文章 · 2019-12-12

13个需要知道的方法:使用 JavaScript 来操作 DOM

作者:Milos Protic
译者:前端小智
来源:impressivewebs.

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

DOM 或文档对象模型是 web 页面上所有对象的根。它表示文档的结构,并将页面连接到编程语言。它的结构是一个逻辑树。每个分支结束于一个节点,每个节点包含子节点、对象。DOM API非常庞大,在本文中,咱们只讨论比较常用有有用的那些API

document.querySelector / document.querySelectorAll

document.querySelector方法返回文档中与指定选择器或选择器组匹配的第一个 html 元素。 如果找不到匹配项,则返回null

document.querySelectorAll 方法返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。返回的对象是 NodeList

// 返回第一个 ul 元素
const list = document.querySelector('ul')
// 返回所有类名为 info 或者 warning 的 div 元素
const elements = document.querySelectorAll('div.info, div.warning');

document.createElement

在一个 HTML 文档中, Document.createElement(tagName) 方法创建由 tagName 指定的 HTML 元素,或一个HTMLUnknownElement,如果tagName不被识别。

Node.appendChild

Node.appendChild()方法将节点添加到给定父节点的子节点列表的末尾。 请注意,如果给定的子代是文档中现有节点的引用,则它将移动到新位置。看看示例:

let list = document.createElement('ul');
['北京', '上海', '深圳'].forEach(city => {
  let listItem = document.createElement('li')
  listItem.innerText = city
  list.appendChild(listItem)
})
document.body.appendChild(list)

Node.insertBefore

此方法在给定的父节点内的子引用节点之前插入给定节点(并返回插入的节点)

伪代码如下所示:

  • 北京
  • 上海
  • 深圳

Node.insertBefore('厦门','北京')

  • 厦门
  • 北京
  • 上海
  • 深圳
let list = document.querySelector('ul');
let firstCity = list.querySelector('ul > li');
let newCity = document.createElement('li');
newCity.textContent = 'San Francisco';
list.insertBefore(newCity, firstCity);

Node.removeChild

Node.removeChild方法从DOM中删除一个子节点并返回删除的节点。 请注意,返回的节点不再是DOM的一部分,而是仍存在于内存中。 如果处理不当,可能会导致内存泄漏。

let list = document.querySelector('ul');
let firstItem = list.querySelector('li');
let removedItem = list.removeChild(firstItem);

Node.replaceChild

此方法替换父节点中的子节点(并返回替换后的旧子节点)。请注意,如果处理不当,此方法可能导致与Node.removeChild类似的内存泄漏问题。

let list = document.querySelector('ul');
let oldItem = list.querySelector('li');
let newItem = document.createElement('li');
newItem.innerHTML = '前端小智';
let replacedItem = list.replaceChild(newItem, oldItem);

Node.cloneNode

Node.cloneNode(deep) 方法返回调用该方法的节点的一个副本,deep(可选)表示是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身.

let list = document.querySelector('ul');
let clone = list.cloneNode();

Element.getAttribute / Element.setAttribute

Element.getAttribute方法返回元素上给定属性的值,反之亦然,Element.setAttribute设置给定元素上属性的值。

let list = document.querySelector('ul');
list.setAttribute('id', 'my-list');
let id = list.getAttribute('id');
console.log(id); // outputs my-list

Element.hasAttribute / Element.removeAttribute

Element.hasAttribute方法检查给定元素是否具有指定的属性,返回值为boolean。 通过调用Element.removeAttribute方法,我们可以从元素中删除具有给定名称的属性。

let list = document.querySelector('ul');
if (list.hasAttribute('id')) {
    console.log('list has an id');
    list.removeAttribute('id');
};

Element.insertAdjacentHTML

element.insertAdjacentHTML(position, text) 将指定的文本解析为HTML或XML,并将结果节点插入到DOM树中的指定位置。它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素。这避免了额外的序列化步骤,使其比直接innerHTML操作更快。

position是相对于元素的位置,并且必须是以下字符串之一:

beforebegin:元素自身的前面。
afterbegin:插入元素内部的第一个子节点之前。
beforeend:插入元素内部的最后一个子节点之后。
afterend:元素自身的后面。

text是要被解析为HTML或XML,并插入到DOM树中的字符串。

<!-- beforebegin -->
<div>
  <!-- afterbegin -->
  <p>Hello World</p>
  <!-- beforeend -->
</div>
<!-- afterend -->

示例:

var list = document.querySelector('ul');
list.insertAdjacentHTML('afterbegin', '<li id="first-item">First</li>');

总结

希望本文对你有所帮助,并且有助于你理解DOM。正确处理DOM树非常重要,如果操作不正确,可能会导致严重后果。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://www.impressivewebs.co...


交流(欢迎加入群,群工作日都会发红包,互动讨论技术)

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

查看原文

赞 20 收藏 14 评论 0

Fundebug 赞了文章 · 2019-12-12

图片该如何优化来提高网站的性能,这里提供几种方法

译者:前端小智
作者:Ayo Isaiah
来源:https://www.freecodecamp.org/

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

为了保证的可读性,本文采用意译而非直译。

图像是web上提供的最基本的内容类型之一。他们说一张图片胜过千言万语。但是如果你不小心的话,图片大小有时高达几十兆。

因此,虽然网络图像需要清晰明快,但它们尺寸可以缩小压缩的,使用加载时间保持在可接受的水平。

在我的网站上,我注意到我的主页的页面大小 超过了 1.1MB,图片占了约88%,我还注意到我提供的图像比它们需要的大(在分辨率方面),显然,还有很多改进的空间。

图片描述

我开始阅读 Addy Osmani 的优秀 Essential Image Optimization电子书,并开始在我的网站上按照他们的建议做了一些图片的优化。,然后再对响应式图像进行了一些研究并应用了它。

这使得页面大小减少到 445kb,约 62% !

图片描述

什么是图像压缩?

压缩图像就是在图片保持在可接受的清晰度范围内同时减少文件大小,我使用 imagemin 来压缩站点上的图像。

要使用 imagemin,确保你已经安装了 Node.js,然后打开一个终端窗口,cd 进入项目,并运行以下命令:

npm install imagemin

然后创建一个名为 imagemin.js 的新文件,写入下面的内容:

const imagemin = require('imagemin');
const PNGImages = 'assets/images/*.png';
const JPEGImages = 'assets/images/*.jpg';
const output = 'build/images';

你可以根据自己的需要更改 PNGImagesJPEGImagesoutput 的值,以符合你的项目结构。

此外要执行图片压缩,还需要根据要压缩的图像类型安装对应的插件。

JPEG/JPG

JPG 的优点

JPG 最大的特点是 有损压缩。这种高效的压缩算法使它成为了一种非常轻巧的图片格式。另一方面,即使被称为“有损”压缩,JPG的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉——前提是你用对了业务场景。

JPG 使用场景

JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。

JPG 的缺陷

有损压缩在上文所展示的轮播图上确实很难露出马脚,但当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。

此外,JPEG 图像不支持透明度处理,透明图片需要召唤 PNG 来呈现。

使用 MozJPEG 压缩 jpeg

这里使用 Mozilla 的 MozJPEG 工具,该工具可以通过 imagemin-mozjpeg 作为 Imagemin 插件使用。你可以通过运行以下命令来安装它:

npm install imagemin-mozjpeg

然后将以下内容添加到的 imagemin.js 中:

const imageminMozjpeg = require('imagemin-mozjpeg');
const optimiseJPEGImages = () =>
  imagemin([JPEGImages], output, {
    plugins: [
      imageminMozjpeg({
        quality: 70,
      }),
    ]
  });
optimiseJPEGImages()
  .catch(error => console.log(error));

可以通过在终端中运行 node imagemin.js 来运行脚本。这将处理所有JPEG图像,并将优化后的版本放 build/images 文件夹中。

我发现将 quality 设置为 70 在大多数情况下可以产生足够清晰的图像,但你的项目需求可能不同,可以自行设置合适的值。

默认情况下,MozJPEG 生成渐进式 jpeg,这会导致图像从低分辨率逐渐加载到高分辨率,直到图片完全加载为止。由于它们的编码方式,它们也比原始的 jpeg 略小。

你可以使用 Sindre Sorhus 提供的这个命令行工具来检查JPEG图像是否是渐进式的。

Addy Osmani 已经很好地总结了使用渐进式 jpeg 的优缺点。对我来说,我觉得利大于弊,所以我坚持使用默认设置。

如果你更喜欢使用原始的jpeg,可以在 options 对象中将 progressive 设置为 false。另外,请确保 imagemin-mozjpeg 版本的变化,请重新查看对应文档。

PNG (PNG-8 与 PNG-24)

PNG 的优缺点

PNG(可移植网络图形格式)是一种无损压缩的高保真的图片格式。8 和 24,这里都是二进制数的位数。按照我们前置知识里提到的对应关系,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。

PNG 图片具有比 JPG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPG 的局限性,唯一的缺点就是 体积太大

PNG 应用场景

前面我们提到,复杂的、色彩层次丰富的图片,用 PNG 来处理的话,成本会比较高,我们一般会交给 JPG 去存储。

考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。

使用 pngquant 优化 PNG 图像

pngquant 是我优化PNG图像的首选工具,你可以通过 imagemin-pngquant 使用它:

npm install imagemin-pngquant

然后将以下内容添加到 imagemin.js 文件中:

const imageminPngquant = require('imagemin-pngquant');
const optimisePNGImages = () =>
  imagemin([PNGImages], output, {
    plugins: [
      imageminPngquant({ quality: '65-80' })
    ],
  });
optimiseJPEGImages()
  .then(() => optimisePNGImages())
  .catch(error => console.log(error));

我发现将 quality 设置为 65-80 可以在文件大小和图像质量之间较好的折衷方案。

有了这些设置,我可以得到一个屏幕截图,我的网站从 913kb 到 187kb,没有任何明显的视觉损失,惊人的79% 的降幅!

这是两个文件。看一看,自己判断一下:

WebP

WebP 的优点

WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身。
WebP 的官方介绍对这一点有着更权威的阐述:

与 PNG 相比,WebP 无损图像的尺寸缩小了 26%。在等效的 SSIM 质量指数下,WebP 有损图像比同类 JPEG 图像小25-34%。 无损 WebP 支持透明度(也称为 alpha 通道),仅需 22% 的额外字节。对于有损 RGB 压缩可接受的情况,有损 WebP 也支持透明度,与 PNG 相比,通常提供 3 倍的文件大小。

将 WebP 图像提供给支持它们的浏览器

WebP 是谷歌引入的一种相对较新的格式,它的目标是通过以无损和有损格式编码图像来提供更小的文件大小,使其成为 JPEG 和 PNG 的一个很好的替代方案。

WebP 图像的清晰度通常可以与 JPEG 和 PNG相提并论,而且文件大小要小得多。例如,当我将屏幕截图从上面转换到 WebP 时,我得到了一个 88kb 的文件,其质量与 913kb 的原始图像相当,减少了90% !

看看这三张图片,你能说出区别吗?

就我个人而言,我认为视觉效果是可以比较的,而且节省下来的大小是不容忽视的。

既然我们已经认识到在可能的情况下使用WebP格式是有价值的,那么很重要的一点是—它不能完全替代 JPEG 和 PNG,因为浏览器对 WebP 支持并不普遍。

在撰写本文时,Firefox、Safari 和 Edge 都是不支持WebP的浏览器。

图片描述

然而,根据 caniuse.com 的数据,全球超过70%的用户使用支持WebP的浏览器。这意味着,通过使用 WebP 图像,可以为大约 70% 的客户提供更快的 web 页面及更好的体验。

安装它,运行以下命令:

npm install imagemin-webp

然后将以下内容添加到你的 imagemin.js 文件中:

const imageminWebp = require('imagemin-webp');
const convertPNGToWebp = () =>
  imagemin([PNGImages], output, {
    use: [
      imageminWebp({
        quality: 85,
      }),
    ]
  });
const convertJPGToWebp = () =>
  imagemin([JPGImages], output, {
    use: [
      imageminWebp({
        quality: 75,
      }),
    ]
  });
optimiseJPEGImages()
  .then(() => optimisePNGImages())
  .then(() => convertPNGToWebp())
  .then(() => convertJPGToWebp())
  .catch(error => console.log(error));

我发现,将 quality 设置为 85 会生成质量与 PNG 相当但小得多的 WebP 图像。对于 jpeg,我发现将 quality 设置为 75 可以在视觉和文件大小之间取得很好的平衡。

提供 HTML格式的WebP图像

一旦有了 WebP 图像,可以使用以下标记将它们提供给可以使用它们的浏览器,同时向不兼容 WebP 的浏览器使用 png 或者 jpeg。

<picture>
    <source srcset="sample_image.webp" type="image/webp">
    <source srcset="sample_image.jpg" type="image/jpg">
    <img data-original="sample_image.jpg" alt="">
</picture>

使用此标记,理解 image/webp 媒体类型的浏览器将下载 Webp 图片并显示它,而其他浏览器将下载 JPEG 图片。

任何不支持 <picture> 的浏览器都将跳过所有 source 标签,并加载底部 img 标签。因此,我们通过提供对所有浏览器类的支持,逐步增强了我们的页面。

图片描述

请注意,在所有情况下,img 标记都是实际呈现给页面的内容,因此它确实是语法的必需部分。 如果省略 img 标记,则不会渲染任何图像。

<picture> 标签和其中定义的所有 source 都在那里,以便浏览器可以选择要使用的图片的路径。 选择源图像后,其 URL 将传给 img 标记,这就是显示的内容。

这意味着你无需设置 <picture>source 标记的样式,因为浏览器不会渲染这些标记。 因此,你可以像以前一样继续使用 img 标签进行样式设置。

总结

正如你所看到的,优化 web 上使用的图像的过程并不复杂,通过减少页面加载时间,可以为客户带来更好的用户体验,希望本文对你有所帮助,共进步!


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:

https://medium.freecodecamp.o...


交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 33 收藏 24 评论 1

认证与成就

  • 获得 3261 次点赞
  • 获得 26 枚徽章 获得 1 枚金徽章, 获得 6 枚银徽章, 获得 19 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2015-01-19
个人主页被 26.4k 人浏览