ES7+ES8

前言

本篇文章主要介绍ES7+ES8的一些新功能,并结合ES6的一些API做出了相应的比较。

ES7

1.Array.prototype.includes()

includes()作用,是查找一个值在不在数组里,若是存在则返回true,不存在返回false.

1.基本用法:

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false
复制代码

2.接收俩个参数:要搜索的值和搜索的开始索引

['a', 'b', 'c', 'd'].includes('b')         // true
['a', 'b', 'c', 'd'].includes('b', 1)      // true
['a', 'b', 'c', 'd'].includes('b', 2)      // false
复制代码

3.与ES6中的indexOf()比较

有些时候是等效的

['a', 'b', 'c'].includes('a')          //true
['a', 'b', 'c'].indexOf('a') > -1      //truevar arr = [1, 2, 3]
var a = 1;
arr.includes(a)   //true
arr.indexOf(a)    //0
复制代码
  • 在判断 +0 与 -0 时,被认为是相同的。
[1, +0, 3, 4].includes(-0)    //true
[1, +0, 3, 4].indexOf(-0)     //1
复制代码
  • 只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些,是无法判断的.
var arr = [1, [2, 3], 4]
arr.includes([2, 3])   //false
arr.indexOf([2, 3])    //-1
复制代码

优缺点比较

  • 简便性

includes()返回的是布尔值,能直接判断数组中存不存在这个值,而indexOf()返回的是索引,这一点上前者更加方便。

  • 精确性

    两者都是采用===的操作符来作比较的,不同之处在于:对于NaN的处理结果不同。

    我们知道js中 NaN === NaN 的结果是false,indexOf()也是这样处理的,但是includes()不是这样的。

    let demo = [1, NaN, 2, 3]demo.indexOf(NaN)        //-1
    demo.includes(NaN)       //true
    复制代码

总结:

由于它对NaN的处理方式与indexOf不同,假如你只想知道某个值是否在数组中而并不关心它的索引位置,建议使用includes()。如果你想获取一个值在数组中的位置,那么你只能使用indexOf方法。

2.求幂运算符

基本用法:

3 ** 2  //9
效果同
Math.pow(3, 2) //9
复制代码

由于是运算符,所以可以和 +=一样的用法

var b = 3;
b **= 2;
console.log(b); //9
复制代码

ES8

1.async await

异步函数async function()

1.1作用

避免有更多的请求操作,出现多重嵌套,也就是俗称的“回调地狱”

this.$http.jsonp('/login', (res) => {this.$http.jsonp('/getInfo', (info) => {// do something})
})
复制代码

因此提出了ES6的Promise,将回调函数的嵌套,改为了链式调用:

var promise = new Promise((resolve, reject) => {this.login(resolve);
})
.then(() => {this.getInfo()
})
.catch(() => {console.log('Error')
})
复制代码

1.2声明方式

异步函数存在以下四种使用形式:

  • 函数声明: async function foo() {}
  • 函数表达式: const foo = async function() {}
  • 对象的方式: let obj = { async foo() {} }
  • 箭头函数: const foo = async () => {}

1.3支持返回Promise和同步的值

async用于定义一个异步函数,该函数返回一个Promise。 如果async函数返回的是一个同步的值,这个值将被包装成一个理解resolve的Promise,等同于return Promise.resolve(value)。 await用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await也可以用于一个同步的值。

    //async await//返回Promiselet timer = async function timer() {return new Promise((reslove, reject) => {setTimeout(() => {reslove('a');}, 1000);})}timer().then(result => {console.log(result);}).catch(err => {console.log(err.message);})//返回同步的值let sayHello = async function sayHello() {let hi = 'hello world'//等同于return Promise.resolve(hi);return hi}sayHello().then(res => {console.log(res)}).catch(err => {console.log(err.message);})
复制代码

1.4对异常的处理

首先来看下Promise中对异常的处理

1.使用reject

let promise = new Promise((reslove, reject) => {setTimeout(() => {reject('promise使用reject抛出异常')  }, 1000)
})
promise().then(res => {console.log(res)
})
.catch(err => {console.log(err)     //'promise使用reject抛出异常'
})复制代码

2.使用new Error()

let promise = new Promise((reslove, reject) => {throw new Error('promise使用Error抛出异常') //使用throw异常不支持放在定时器中
})
promise().then(res => {console.log(res)
})
.catch(err => {console.log(err.message)     //'promise使用Error抛出异常'
})复制代码

3.reject一个new Error()

   let promise = new Promise((resolve, reject) => {setTimeout(() => {reject(new Error('promise抛出异常'));}, 1000);})promise.then(res => {console.log(res);}).catch(err => {console.log(err.message);  //'promise抛出异常'})
复制代码

async对异常的处理也可以直接用.catch()捕捉到

   //async抛出异常let sayHi = async sayHi => {throw new Error('async抛出异常');}sayHi().then(res => {console.log(res);}).catch(err => {console.log(err.message);})
复制代码

和Promise链的对比:

我们的async函数中可以包含多个异步操作,其异常和Promise链有相同之处,如果有一个Promise被reject()那么后面的将不会再进行。

    let count = () => {return new Promise((resolve, reject) => {setTimeout(() => {reject('promise故意抛出异常')}, 1000);})}let list = () => {return new Promise((resolve, reject) => {setTimeout(() => {resolve([1, 2, 3])}, 1000);})}let getList = async () => {let c = await count()console.log('async')    //此段代码并没有执行let l = await list()return { count: c, list: l }}console.time('start');getList().then(res => {console.log(res)}).catch(err => {console.timeEnd('start')console.log(err)})//start: 1000.81494140625ms//promise故意抛出异常
复制代码

可以看到上面的案例,async捕获到了一个错误之后就会立马进入.catch()中,不执行之后的代码

1.5并行

上面的案例中,async采用的是串行处理

count()和list()是有先后顺序的

let c = await count()
let l = await list()
复制代码

实际用法中,若是请求的两个异步操作没有关联和先后顺序性可以采用下面的做法

let res = await Promise.all([count(), list()])
return res//res的结果为
//[ 100, [ 1, 2, 3 ] ]
复制代码

案例详情为:

let count = ()=>{return new Promise((resolve,reject) => {setTimeout(()=>{resolve(100);},500);});
}let list = ()=>{return new Promise((resolve,reject)=>{setTimeout(()=>{resolve([1,2,3]);},500);});
}let getList = async ()=>{let result = await Promise.all([count(),list()]);return result;
}
console.time('begin');
getList().then(result => {console.timeEnd('begin');  //begin: 505.557msconsole.log(result);       //[ 100, [ 1, 2, 3 ] ]
}).catch(err => {console.timeEnd('begin');console.log(err);
});
复制代码

我们将count()和list()使用Promise.all()“同时”执行,这里count()和list()可以看作是“并行”执行的,所耗时间将是两个异步操作中耗时最长的耗时。 最后得到的结果是两个操作的结果组成的数组。我们只需要按照顺序取出数组中的值即可。

1.6与Generator的关系

先来回顾一下ES6中Generator函数的用法:

  function* getList() {const c = yield count()const l = yield list()return 'end'}var gl = getList()console.log(gl.next()) // {value: Promise, done: false}console.log(gl.next()) // {value: Promise, done: false}console.log(gl.next()) // {value: 'end', done: true}
复制代码

虽然Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。此时,我们便希望能出现一种能自动执行Generator函数的方法。我们的主角来了:async/await。

ES8引入了async函数,使得异步操作变得更加方便。简单说来,它就是Generator函数的语法糖。

let getList = async () => {const c = await count()const l = await list()
}
复制代码

2.Object.entries()

2.1作用

作用:将一个对象中可枚举属性的键名和键值按照二维数组的方式返回。

若对象是数组,则会将数组的下标作为键值返回。

Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.entries([1, 2])                //[['0', 1], ['1', 2]]
复制代码

2.2要点

1.若是键名是Symbol,编译时会被自动忽略

Object.entries({[Symbol()]:1, two: 2})  //[['two', 2]]
复制代码

2.entries()返回的数组顺序和for循环一样,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果

Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]
复制代码

3.利用Object.entries()创建一个真正的Map

    var obj = { foo: 'bar', baz: 42 };var map1 = new Map([['foo', 'bar'], ['baz', 42]]); //原本的创建方式var map2 = new Map(Object.entries(obj));    //等同于map1console.log(map1);// Map { foo: "bar", baz: 42 }console.log(map2);// Map { foo: "bar", baz: 42 }
复制代码

2.3自定义Object.entries()

Object.entries的原理其实就是将对象中的键名和值分别取出来然后推进同一个数组中

    //自定义entries()var obj = { foo: 'bar', baz: 42 };function myEntries(obj) {var arr = []for (var key of Object.keys(obj)) {arr.push([key, obj[key]])}return arr}console.log(myEntries(obj))//Generator版本function* genEntryies(obj) {for (let key of Object.keys(obj)) {yield [key, obj[key]]}}var entryArr = genEntryies(obj);console.log(entryArr.next().value) //["foo", "bar"]console.log(entryArr.next().value) //["baz", 42]
复制代码

3.Object.values()

3.1作用

作用:只返回自己的键值对中属性的值。它返回的数组顺序,也跟Object.entries()保持一致

Object.values({ one: 1, two: 2 })            //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' })    //['c', 'a', 'b']
复制代码

3.2与Object.keys()比较

ES6中的Object.keys()返回的是键名

    var obj = { foo: 'bar', baz: 42 };console.log(Object.keys(obj)) //["foo", "baz"]console.log(Object.values(obj)) //["bar", 42]//Object.keys()的作用就类似于for...infunction myKeys() {let keyArr = []for (let key in obj1) {keyArr.push(key)console.log(key)}return keyArr}console.log(myKeys(obj1)) //["foo", "baz"]
复制代码

3.3entries()、values()总结

    var obj = { foo: 'bar', baz: 42 };console.log(Object.keys(obj)) //["foo", "baz"]console.log(Object.values(obj)) //["bar", 42]console.log(Object.entries(obj)) //[["foo", "bar"], ["baz", 42]]
复制代码

4.字符串填充

4.1padStart()和padEnd()

字符串填充padStart()padEnd()

用法

String.padStart(targetLength, padding)

参数:字符串目标长度和填充字段

'Vue'.padStart(10)           //'       Vue'
'React'.padStart(10)         //'     React'
'JavaScript'.padStart(10)    //'JavaScript'
复制代码

4.2要点

1.填充函数只有在字符长度小于目标长度时才有效,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出

'Vue'.padEnd(10, '_*')           //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello')      //'ReactHello'
'JavaScript'.padEnd(10, 'Hi')    //'JavaScript'
'JavaScript'.padEnd(8, 'Hi')     //'JavaScript'
复制代码

5.Object.getOwnPropertyDescriptors()

5.1作用

该方法会返回目标对象中所有属性的属性描述符,该属性必须是对象自己定义的,不能是从原型链继承来的。

    var obj = {id:  1,name: '霖呆呆',get gender() {console.log('gender')},set grad(d) {console.log(d)}}console.log(Object.getOwnPropertyDescriptors(obj))//输出
{gender: {configurable: true,enumerable: true,get: f gender(),set: undefined},grade: {configurable: true,enumerable: true,get: undefined,set: f grade(g)},id: {configurable: true,enumerable: true,value: 1,writable: true},name: {configurable: true,enumerable: true,value: '霖呆呆',writable: true}
}
复制代码

第二个参数,用于指定属性的属性描述符

Object.getOwnPropertyDescriptors(obj, 'id')//输出结果应该为
{id: {configurable: true,enumerable: true,value: 1,writable: true}
}
复制代码

但是我在谷歌/火狐浏览器试了好像没有效果,有知道原因的小伙请留言

5.2与getOwnPropertyDescriptor()比较

ES6中也有一个返回目标对象可枚举属性的方法

var obj = {id: 1,name: '霖呆呆',get gender() {console.log('gender')},set grad(d) {console.log(d)}
}
console.log(Object.getOwnPropertyDescriptor(obj, 'id'))//输出结果{id: {configurable: true,enumerable: true,value: 1,writable: true}
}
复制代码

两者的区别:一个是只返回知道属性名的描述对象,一个返回目标对象所有自身属性的描述对象

5.3自定义该方法

        function myDescriptors(obj) {let descriptors = {}for (let key in obj) {descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)}return descriptors}console.log(myDescriptors(obj))//返回的结果和该方法一样//其中上面自定义方法的for...in也可以换成,效果也是一样的for (let key of Object.keys(obj)) {descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)}
复制代码

6.函数参数支持尾部逗号

该特性允许我们在定义或者调用函数时添加尾部逗号而不报错

        let foo = function (a,b,c,) {console.log('a:', a)console.log('b:', b)console.log('c:', c)}foo(1, 3, 4, )//输出结果为:a: 1b: 3c: 4
复制代码

它适用于那种多行参数并且参数名很长的情况,开发过程中,如果忘记删除尾部逗号也没关系,ES8已经支持这种写法。

7.修饰器Decorator

ES8神器Decorator,修饰器,也称修饰器模式

7.1 伪Decorator

在介绍Decorator之前,我们先来实现这样一个功能:

定义一个函数,在调用这个函数时,能够执行一些其他额外操作

如下代码,定义doSometing(),在调用它时再执行其他代码

        function doSometing(name) {console.log('Hello' + name)}function myDecorator(fn) {return function() {console.log('start')const res = fn.apply(this, arguments)console.log('end')return res}}const wrapped = myDecorator(doSometing)doSometing('lindaidai')//Hellowlindaidaiwrapped('lindaidai')//start //Hellowlindaidai//end
复制代码

可以看到上面的操作:其实就是一个函数包装成另一个函数,这样的方式我们称之为“修饰器”

同理,我们是不是能用一个什么东西附着在我们的类或者类的属性上,让它们也有一些附加的属性或者功能呢,比如这样:

@addSkill
class Person { }function addSkill(target) {target.say = "hello world";
}
复制代码

Person这个类中,开始定义的时候是什么属性都没有的,在其上面使用@来附着上一个函数,这个函数的功能是给目标对象添加额外的属性say

这样Person这个类就有了say这个属性了。

此时控制台输出:

console.log(Person['say']) //'hello world'
复制代码

同样的,如果想使用Person这个类创建出来的对象也能附加上一些属性,可以在目标对象的原型对象中进行添加:

@addSkill
class Person { }function addSkill(target) {target.say = "hello world"; //直接添加到类中target.prototype.eat = "apple"; //添加到类的原型对象中
}
var personOne = new Person()console.log(Person['say']) // 'hello world'
console.log(personOne['eat']) // 'apple'
复制代码

上面案例中的@addSkill其实就是一个最简单的修饰器。

当然,如果你将上面案例中的代码复制到你html文件中,会发现它并不能如愿的执行:

那是因为decorator是es7提供的方法,在浏览器中是无法直接运行的,如果你想要使用它,我们需要提前做一些准备,对它进行编译。

如果你不想深入其中,只是想单纯的了解并使用它可以参考下面的简易教程。

7.2 快速使用

网上使用Decorator的教材有很多,大多都是要需要使用插件来让浏览器支持Decorator。这里长话短说,贴上一个最精简的使用教程:

1.创建一个名为:Decorator的文件夹

2.在文件夹目录下执行命令行

npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
复制代码

此时文件夹下会出现俩个文件: node_modules 依赖文件夹和package.json-lock.json

3.创建文件 complie.js

require('babel-register')({plugins: ['transform-decorators-legacy']
});
require("./app.js")
复制代码

4.创建文件 app.js

@addSkill
class Person { }
function addSkill(target) {target.say = "hello world";
}
console.log(Person.say)   //'hello world'
复制代码

5.在根目录下执行指令:

node complie.js
复制代码

此时可以看到命令行中打印出了 hello world

简单介绍下上面步骤的原理:

第二步中使用了俩个基础插件:

transform-decorators-legacy:
//是第三方插件,用于支持decoratorsbabel-register:
//用于接入node api
复制代码

第三步、第四步创建的俩个文件

complie.js  //用来编译app
app.js   //使用了装饰器的js文件
复制代码

第五步:

原理:
1,node执行complie.js文件;
2,complie文件改写了node的require方法;
3,complie在引用app.js,使用了新的require方法;
4,app.js在加载过程中被编译,并执行。
复制代码

当然你也可以将app.js替换为app.ts 不过别忘了把complie.js中的app.js修改为app.ts

// app.ts
@addSkill
class Person { }
function addSkill(target) {target.say = "hello world";
}
console.log(Person['say'])
//这里如果直接使用Person.say会提示say属性不存在,如我使用的vscode编辑器就会报错,是因为ts的原因,只需要用[]的形式获取对象属性即可。
复制代码

注:ts中有些语法是和js中不一样的,比如有些对象上提示没有属性的时候,只需要换一种获取对象属性的方式即可。

7.3 类修饰器

直接作用在类上面的修饰器,我们可以称之为类修饰器。

如上面案例中的@addSkill就是一个类修饰器,它修改了Person这个类的行为,为它加上了静态属性say

addSkill函数的参数target是Person这个类本身。

1.修饰器的执行原理基本就是这样:

@decorator
class A {}// 等同于class A {}
A = decorator(A) || A;
复制代码

换句话说,类修饰器是一个对类进行处理的函数。

它的第一个参数target就是函数要处理的目标类。

2.多参数

当然如果你想要有多个参数也是可以的,我们可以在修饰器外面再封装一层函数:

@addSkill("hello world")
class Person { }
function addSkill(text) {return function(target) {target.say = text;}
}
console.log(Person.say)  //'hello world'
复制代码

上面代码中,修饰器addSkill可以接受参数,这就等于可以修改修饰器的行为。

3.修饰器在什么时候执行。

先来看一个案例:

@looks
class Person { }
function looks(target) {console.log('I am handsome')target.looks = 'handsome'
}console.log(Person['looks'])//I am handsome
//handsome
复制代码

在修饰器@looks中添加一个console.log()语句,却发现它是最早执行的,其次才打印出handsome

这是因为装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

装饰器是在编译时就执行的函数

7.4 方法修饰器

上面的案例中,修饰器作用的对象是类本身。

当然修饰器不仅仅这么简单,它也可以作用在类里的某个方法或者属性上,这样的修饰器我们称它为方法修饰器。

如下面的案例:

class Person {constructor() {}@myname  //方法修饰器name() {console.log('霖呆呆') }
}
function myname(target, key, descriptor) {console.log(target);console.log(key);console.log(descriptor);descriptor.value = function() {console.log('霖呆呆')}
}var personOne = new Person() //实例化
personOne.name() //调用name()方法//打印结果:
Person {}
name
{ value: [Function: name],writable: true,enumerable: false,configurable: true }
霖呆呆
复制代码

上面案例中的修饰器@myname是放在name()方法上的,myname函数有三个参数:

target: 类的原型对象,上例是Person.prototype
key: 所要修饰的属性名  name
descriptor: 该属性的描述对象
复制代码

我们改变了descriptor中的value,使之打印出霖呆呆。

7.5 多个修饰器的执行顺序

若是同一个方法上有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。

class Person {constructor() {}@dec(1)@dec(2)name() {console.log('霖呆呆')}
}
function dec(id) {console.log('out', id);return function(target, key, descriptor) {console.log(id);}
}var person = new Person()
person.name()
//结果
out 1
out 2
2
1
霖呆呆
复制代码

如上所属,外层修饰器dec(1)先进入,但是内层修饰器dec(2)先执行。

7.6 不能作用于函数

修饰器不能作用于函数之上,这是因为函数和变量一样都会提升

var counter = 0;var add = function () {counter++;
};@add
function foo() {
}
复制代码

如上面的例子所示,给函数foo()定义了修饰器@add,作用是想将counter++

预计的结果counter为1,但实际上却还是为0

原因:

定义的函数foo()会被提升至最上层,定义的变量counteradd也会被提升,效果如下:

@add
function foo() {
}var counter;
var add;counter = 0;add = function () {counter++;
};
复制代码

总之,由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。

另一方面,如果一定要修饰函数,可以采用高阶函数的形式直接执行。

如在7.1中的例子所示:

        function doSometing(name) {console.log('Hello' + name)}function myDecorator(fn) {return function() {console.log('start')const res = fn.apply(this, arguments)console.log('end')return res}}const wrapped = myDecorator(doSometing)doSometing('lindaidai')//Hellowlindaidaiwrapped('lindaidai')//start //Hellowlindaidai//end
复制代码

后语

知识无价,尊重原创。

参考文集:

10分钟学会ES7+ES8

JavaScript中的装饰器--Decorator

es7-decorator修饰器运行环境搭建及实践

阮一峰Decorator详解

Javascript之ES7详解相关推荐

  1. javascript BOM对象详解

    javascript BOM对象详解 目标:本章节将分为9点详细介绍有关BOM对象的知识点 1.什么是BOM 2.BOM的构成 3.顶级对象window 4.window对象常见事件(页面加载事件和体 ...

  2. JavaScript 运行机制详解(理解同步、异步和事件循环)

    1.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

  3. 好程序员技术分析JavaScript闭包特性详解

    为什么80%的码农都做不了架构师?>>>    好程序员技术分析JavaScript闭包特性详解,今天来总结一下js闭包的那些事,以及遇到的坑和解决方法,希望对你有所帮助. 是的,没 ...

  4. 我看朴灵评注阮一峰的《JavaScript 运行机制详解:再谈Event Loop》

    阮一峰和朴灵对我来说都是大牛,他们俩的书我都买过,阮老师的译作<软件随想录>和朴灵的<深入浅出node.js>.这个事情已经过了4个月了,所以我拿来讲应该也没啥问题. 这件事情 ...

  5. JavaScript日期时间详解

    JavaScript日期时间详解 日期与时间 获取时间对象 var date=new Date() 时间的获取方法 date.toString() date.toLocaleString() date ...

  6. javaScript函数模块详解

    javaScript函数模块详解 ​ 函数实际上是对象,每个函数都是Function类型的实例,而Function也有属性和方法,和其他引用类型一样 可以将函数名想象成指针,函数想象成对象 ​ 注意, ...

  7. 视频教程-javascript/jquery全过程详解-Java

    javascript/jquery全过程详解 资深大数据.java讲师,十年开发经验,曾经任职于北大青鸟.讯腾软件等多家知名教育机构,精通javaweb, 前端技术,J2EE技术体系,熟练使用Spri ...

  8. 史上最全JavaScript数组对象详解(二)

    JavaScript数组对象详解(二) 上一篇博客我们讲到了JavaScript数组对象的创建,访问和属性,接下来一篇博客主要讲一下JavaScript数组对象的方法及使用.说到数组的方法,主要分为两 ...

  9. JavaScript的函数详解

    JavaScript的函数详解 一.什么是函数 二.函数的使用 1.函数的声明 2.函数的调用 3.函数的参数:参入运算的数据 (1)形参(形式参数) (2)实参(实在参数): (3)参数之间的数据传 ...

最新文章

  1. web前端 vue、react、angular三大框架对比 与jquery的对比
  2. ajax 同步和异步
  3. 【CodeForces - 129C】Statues(思维,bfs)
  4. netbeans搭建安卓开发环境
  5. 五笔字根表识别码图_王码86版五笔字根表口诀助记词(完整大图)
  6. C基础:画直线+源码
  7. 匿名mahony互补滤波代码详解
  8. VUE + 微信分享
  9. php过滤特殊表情符号
  10. Python绘制用于学术论文投稿的黑白图片
  11. 命令查看yarn当前任务列表
  12. 学习篇--FPGA学习网站
  13. webRTC H265浏览器播放器+metaRTC推流实现webRTC H265解决方案
  14. 家族关系查询系统程序设计算法思路_大数据结构课程设计(家族关系查询系统)要点...
  15. PHP实现RSA算法
  16. application.yml与bootstrap.yml的区别
  17. js随机字符a-zA-Z0-9
  18. 购买阿里云服务器后怎么用?针对新手的阿里云服务器教程
  19. 豆瓣影评爬虫:cutecharts数据可视化看看大家对八佰的评价如何
  20. Notepad++ 下载安装和使用

热门文章

  1. xshell连接kali时vim无法粘贴解决方法
  2. 两栈共享存储空间算法
  3. 搭建一个jumpserver跳板机
  4. 移位运算符:,,总结
  5. lucene-5.3.1配置(win7x64)
  6. CocoaPods pod install
  7. ExtTabMenu 控件
  8. 未来已来!医院数字化转型为“看病难”画上“休止符”
  9. STM32之外部中断例程
  10. 线程池最佳线程数量到底要如何配置?