gnipbao / iblog Goto Github PK
View Code? Open in Web Editor NEW🖋 iblog=issues blog 欢迎订阅(watch👀) 收藏(star)
Home Page: https://github.com/gnipbao/iblog/issues
🖋 iblog=issues blog 欢迎订阅(watch👀) 收藏(star)
Home Page: https://github.com/gnipbao/iblog/issues
var addEvent = function(elem, type, handler) {
if (window.addEventListener) {
addEvent = function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
} else if (window.attachEvent) {
addEvent = function(elem, type, handler) {
elem.attachEvent('on' + type, handler);
}
}
addEvent(elem, type, handler);
};
接收多个函数作为参数,从右到左,一个函数的输入为另一个函数的输出。
const compose = (f, g) => (a) => f(g(a))//结合律
//f 和 g 都是函数,a 是在它们之间通过“管道”传输的值
const floorAndToString = compose((val) => val.toString(), Math.floor) // 使用
floorAndToString(12.12) //=> '12'
function reducer(state, action){
return action(state);
}
function reverse(str){
return str.split('').reverse().join('');
}
function upperCase(str){
return str.toUpperCase();
}
let str = [reverse, upperCase].reduce(reducer, "hello world!");//=>!DLROW OLLEH
将一个多元函数转变为一元函数的过程。 每当函数被调用时,它仅仅接收一个参数并且返回带有一个参数的函数,直到传递完所有的参数
//柯里化函数逐渐返回消耗参数的函数,直到所有参数耗尽
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(3)(4) // 7
const add2 = curriedSum(2)
add2(10) // 12
lodash,understore 和 ramda 有 curry 函数可以自动完成柯里化。
function curry(fn){
return function(arg){
return fn(arg)
}
}
// 接受一个函数
//返回一个只接受一个参数的函数
//似乎是一个没用的函数?
parseInt(11);//=>11
parseInt(11,2);//=>3
['11','11','11','11'].map(parseInt);//=>[11,NaN,3,4]
['11','11','11','11'].map(curry(parseInt));//=>[11,11,11,11]
//显示控制接受固定以及可选的参数的函数行为
function curry2(fn){
return function(secondArg){
return function(firstArg){
return fn(firstArg, secondArg)
}
}
}
function curry3(fn){
return function(last){
return function(middle){
return function(first){
return fn(first, middle, last);
}
}
}
}
function curryN(fn){
//...
}
部分应用也叫偏函数应用是指使用一个函数并将其应用一个或多个参数,但不是全部参数,在这个过程中创建一个新函数。
// partical application
function apply(fn /* partial arguments... */ ) {
var args = [].slice.call(arguments, 1);
return function() {
var _args = [].slice.call(arguments);
return fn.apply(this, args.concat(_args));
}
}
// es6
function apply(fn, ...args) {
return (..._args) => {
return fn(...args, ..._args);
};
}
// 创建偏函数,固定一些参数
const partical = (f, ...args) =>
// 返回一个带有剩余参数的函数
(...moreArgs) =>
// 调用原始函数
f(...args, ...moreArgs)
const add3 = (a, b, c) => a + b + c
// (...args) => add3(2, 3, ...args)
// (c) => 2 + 3 + c
const fivePlus = partical(add3, 2, 3)
fivePlus(4) // 9
f(...args, ...moreArgs)
const add3 = (a, b, c) => a + b + c
// (...args) => add3(2, 3, ...args)
// (c) => 2 + 3 + c
const fivePlus = partical(add3, 2, 3)
fivePlus(4) // 9
function isOdd(v) {
if (v === 0) return false;
return isEven( Math.abs( v ) - 1 );
}
function isEven(v) {
if (v === 0) return true;
return isOdd( Math.abs( v ) - 1 );
}
isOdd( 33333 ); // RangeError: Maximum call stack size exceeded
指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
// case1 不属于
function f(x){
let y = g(x);
return y;
}
// case2 不属于
function f(x){
return g(x) + 1;
}
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}//复杂度 O(n)
factorial(5) // 120
//尾递归
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}//复杂度 O(1)
factorial(5, 1) // 120
function fibonacci (n) {
return n <= 1 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(40); // 165580141
//经过尾调优化后
function fibonacci (n, ac1, ac2) {
(ac1 = ac1 || 1), (ac2 = ac2 || 1);
return n <= 1 ? ac2 :fibonacci(n - 1, ac2, ac1 + ac2);
}
fibonacci(100); // 573147844013817200000
fibonacci(1000); // 7.0330367711422765e+208
fibonacci(10000); // Infinity
//转化成while循环
function factorial (n) {
var fact = 1;
while (n > 1) {
fact *= n--;
}
return fact;
}
function fibonacci (n) {
var ac1, ac2, tmp,
i = ac2 = ac1 = 1;
while(n > i++) {
(tmp = ac2),
(ac2 = ac1 + ac2),
(ac1 = tmp);
}
return ac2;
}
顾名思义,蹦床函数就是随着递归执行的开始和结束,调用栈会出现入栈、出栈效果,就像是在弹蹦床。
//utility
function trampoline(fun /*, args */) {
var result = fun.apply(fun, _.rest(arguments));
while (_.isFunction(result)) {
result = result();
}
return result;
}
黑客们解决问题,建设事物,他们崇尚自由和无私的双向帮助。要被他人承认是一名黑客,你必须表现得你具备了这样的态度。而要表现得你具备了这种态度,你必须彻彻底底的坚持它。
如果你认为培养黑客的态度只是一条在这个文化圈中得到认同的路子,那就错了。成为具备这种素质的人对 你 ¸非常重要 —— 使你保持学习和成为黑客的自发性。正如所有创造性艺术一样,成为大师的最有效途径就是效仿大师的精神——不仅从理念上,还要从态度上效仿。
或许下面的这首现代禅诗很好的阐述了这个意思:
To follow the path:
沿着这样一条道路:
look to the master,
关注大师,
follow the master,
跟随大师,
walk with the master,
与大师同行,
see through the master,
洞察大师,
become the master.
成为大师。
如果你想成为一名黑客,反复阅读以下内容直到你相信它们:
作为一名黑客可以享受很多乐趣,同时需要付出相当多的努力。努力需要动力。成功的运动员从锻炼身体、超越身体极限中获得精神愉悦。类似的,作为一名黑客,你可以从解决问题、磨练技术和锻炼智力中获得乐趣。
如果你天生不是这样的人,那你需要设法变成这样的人以使你能够成为一名黑客。否则你将会发现你的精力会被诸如性、金钱、社会上的虚名之类让人分心的东西所消磨掉。
(你还需要对自己的学习能力树立信心——相信金关当你对某一问题了解得不多,只要你能解决其中一部分,并从中学习,你可以解决其他的部分——直到解决它。)
创造性的智慧是非常有价值且稀缺的资源。它们不应当被浪费在重复发明轮子上,世上仍有大量迷人的新问题等着解决。
作为一名黑客,你应该坚信其他黑客的时间非常宝贵——所以你有义务共享信息,解决问题之后公布方案,这样其他人可以去解决新的问题,而不是忙于应付旧问题。
注意,“同一个问题不应该被重复处理两次”并不是说你必须认为所有已有方案都是最优的,或每个问题只有唯一的解决方案。通常我们从一个问题的最初解决方案中能够学习到很多东西。这很好,并且对于我们思考如何能做得更好来说,这通常是必要的。我们反对的是人为的技术、法律上的,或者机构性的设置障碍(例如闭源软件),使得一个好的方案不能被重复使用,逼得人们重造轮子。
(你不必认为你必须将 所有 你的创造发明都公布出去,虽然这样做的黑客将会赢得大家极度尊重。适当卖一些钱来换取足够的食物、租金和电脑并不违反黑客的价值观。用你的技能来养家餬口甚至致富都可以,只要你在做这些的时候别忘记你是一名黑客。)
黑客(以及富有创造力的所有人)不应当被愚蠢的重复性劳动所困扰,因为这意味着他们并没有在做只有他们才能做的事情——解决新问题。这样的浪费会伤害所有人。因此,无聊和乏味的工作不仅仅是令人不爽,而是罪恶。
作为一个黑客,你应该坚信这一点并尽可能的将枯燥的工作自动化,这不仅仅是为了你自己,也为了其他人(尤其是其他黑客)。
(这里有一个例外。黑客有时会做一些看起来重复或枯燥的事情以进行脑力休息,或以此来锻炼一种技能,或以此获得某种除此以外无法获取的经验。但这是有选择的——有脑子的人不该被强迫做枯燥的事。)
黑客是天生的反**主义者。 任何能向你发号施令的人能够迫使你停止解决令你着迷的问题。 同时,按照**者的一般思路,他通常会给出一些极端愚昧的理由。因此,不论何处,任何**主义的作法,只要它压迫你和其他黑客,你就要和它斗到底。
(这并非向所有权威挑战。儿童需要监护,罪犯要被看管起来。如果服从命令得到某种东西比起用其他方式得到它更节约时间,黑客可以同意 接受某种形式的权威。但这是一个有限度的,有意的交易;那种权威想要的个人服从不是你应该同意给予的。)
权威喜欢审查和保密。他们不信任自愿的合作和信息共享——他们只喜欢由他们控制的所谓“合作”。因此,作为一个黑客,你得对审查、保密,以及使用武力或欺骗去压迫有行为能力的人们的做法有一种本能的敌意。 同时你要有为此信念斗争的意愿。
作为一个黑客,你必须培养起这些精神。但是仅仅有精神并不能使你成为黑客,也不能使你成为运动健将或摇滚明星。成为一名黑客还需要智力,实践,奉献精神和辛勤工作。
因此,你需要学会有怀疑态度和尊重任何能力。黑客不会为装模作样的人浪费时间,但他们尊重能力——尤其是从事黑客工作的能力,不过任何能力都是好的。很少人能具备的高要求能力尤其好,其中涉及脑力,技巧和专注方面的能力最好。尊重能力,你就会享受到提高自己的能力所带来的乐趣。
少量数据使用超出异常
/**
* little arrays (>=100k characters), otherwise you'll get exceptions as RangeError : Maximum call
* stack size exceeded or Out of stack space.
* @returns {string}
*/
function uint8arrayToStringMethod(myUint8Arr){
return String.fromCharCode.apply(null, myUint8Arr);
}
浏览器环境使用TextEncoder 和 TextDecoder 原生方法实现,注意兼容性。
/**
* Convert an Uint8Array into a string.
*
* @returns {String}
*/
function Decodeuint8arr(uint8array){
return new TextDecoder("utf-8").decode(uint8array);
}
/**
* Convert a string into a Uint8Array.
*
* @returns {Uint8Array}
*/
function Encodeuint8arr(myString){
return new TextEncoder("utf-8").encode(myString);
}
跨平台依赖性低运行良好
// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
/* utf.js - UTF-8 <=> UTF-16 convertion
*
* Copyright (C) 1999 Masanao Izumo <[email protected]>
* Version: 1.0
* LastModified: Dec 25 1999
* This library is free. You can redistribute it and/or modify it.
*/
function Utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;
out = "";
len = array.length;
i = 0;
while(i < len) {
c = array[i++];
switch(c >> 4)
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
}
大量数据异步情况
/**
* Converts an array buffer to a string
*
* @param {Uin8} uint8arr | The buffer to convert
* @param {Function} callback | The function to call when conversion is complete
*/
function largeuint8ArrToString(uint8arr, callback) {
var bb = new Blob([uint8arr]);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
};
f.readAsText(bb);
}
// Usage example
// "Hello" in Uint8Array format
var myuint8Arr = new Uint8Array([72, 101, 108, 108, 111, 32, 33]);
largeuint8ArrToString(myuint8Arr,function(text){
// Hello
console.log(text);
});
// async tasks with return promise
let runPromiseSequence = (asyncTasks) => {
let reducer = (acc, curr) => acc.then(() => curr());
return asyncTasks.reduce(reducer, Promise.resolve());
};
runPromiseSequence.then((ret)=>{
// todo
})
// promises
let runPromiseSequence = (tasks) => {
return tasks.reduce(async (curr, next) => {
await curr;
return next();
}, Promise.resolve());
};
runPromiseSequence.then((ret)=>{
// todo
})
reduce里的第一个函数相当于一个reducer可以传递4个参数:
第二个参数可以理解为acc的初始值,在reduce执行时候先会赋值acc初始值initValue也就是一个Promise.resolve()
,curr赋值为当前tasks的第一个值也就是数组中第一个异步函数,由于tasks是一个promise异步任务函数 ,这里使用Promise.resolve()
来触发执行,这时候会触发acc.then()
然后执行curr也就是第一个异步函数执行并继续返回一个promise赋值给acc,curr取下一个异步函数这样就能实现串行执行每个task。
第二种async只不过是将reducer函数使用aysnc函数来实现本质上是类似的只是代码看起来简单很多。
注意reducer中第一个参数其实是一个执行后的结果这里是Promise而非函数。
并行比较简单使用Promise.all
原生api既可以实现。
const createPromise = (time, id) => () =>
new Promise(solve =>
setTimeout(() => {
console.log(`p=>${id}`);
solve();
}, time)
);
runPromiseSequence([
createPromise(3000, 1),
createPromise(2000, 2),
createPromise(1000, 3)
])
可以在控制台通过打印顺序可以看出每个任务串行执行。
Promise.all([
createPromise(3000, 1)(),
createPromise(2000, 2)(),
createPromise(1000, 3)()
]).then((ret)=>console.log);
并行执行
数组是类似列表的对象,在原型中提供了一些遍历以及改变其中对象的方法。JavaScript数组的长度及其中元素的类型都是不固定的。因为数组的长度可以随时增长或缩减,所以JavaScript 数组不保证是密集的。通常情况下,这是一些方便的特性;如果这些特性不适用于你的特定使用场景,你可以考虑使用固定类型数组。在JavaScript中数组可以使用数组构造函Array
被创建,为了方便一般用中括号[]
创建,Arrays
继承Object
的原型.typeof
检测数组的类型会返回object
,可是使用[]instanceof Array,会返回true,也就是说在JavaScript中存在类似数组的Array-like
的对象objects
,例如:strings,arguments
对象,arguments对象不是数组的实例,但是它有length
属性,它的值和数组的下标属性之间是有关系的并且length是可变的(例如, push, splice, 等) 会改变Array
的 length
属性。因此它可以像任何数组一样去遍历.这篇文章主要介绍一些Array.prototype
中常用的方法,尽可能揭开每一种方法的真面纱。
你可以在你的浏览器控制台里面尝试!
.forEach
循环.some
和.every
检测.join
和.concat
细微差别.pop
,.push
,.shift
,和.unshift
模拟堆栈.map
映射.filter
过滤测试.sort
排序.reduce
,.reduceRight
累加器.slice
复制(copy).splice
修改(update).indexOf
查找.reverse
逆序array.forEach(callback[,thisArg])
forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者从未赋值的项将被跳过(但不包括哪些值为 undefined 的项),这个给定的回调函数可以传入三个参数
value
数组当前项的值index
当前的索引(或下标)array
数组本身['_', 't', 'a', 'n', 'i', 'f', ']'].forEach(function (value, index, array) {
this.push(String.fromCharCode(value.charCodeAt() + index + 2))
}, out = [])
out.join('')
//<-'awesome'
array.some(callback[,thisArg])
some 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值,some 将会立即返回 true
。否则,some 返回 false
。callback 只会在那些”有值“的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组。some调用不改变数组。some 遍历的元素的范围在第一次调用 callback. 时就已经确定了。在调用 some 后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some 访问到它那一刻的值。
max = -Infinity
satisfied = [10, 12, 10, 8, 5, 23].some(function (value, index, array) {
if (value > max) max = value
return value < 10
})
console.log(max)
// <- 12
satisfied
// <- true
array.every(callback[,thisArg])
every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个使 callback 返回 false(表示可转换为布尔值 false 的值)的元素。如果发现了一个这样的元素,every 方法将会立即返回 false。否则,callback 为每一个元素返回 true,every 就会返回 true。callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从来没被赋值的索引调用。every也不会改变数组,遍历元素的范围和值与some基本一样。
function isBigEnough(element, index, array) {
return (element >= 10);
}
var passed = [12, 5, 8, 130, 44].every(isBigEnough);
// passed is false
passed = [12, 54, 18, 130, 44].every(isBigEnough);
// passed is true
总结下:some其实就相当与检测数组中是否存在使callback返回true
的值.而every是检测数组中任意值都能使callback返回true
.这里的true
指的是可以转换为布尔值的元素.
str = arr.join([separator = ','])
join方法将所有的数组元素被转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是undefined 或者null,则会转化成空字符串。
var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join(); // myVar1的值变为"Wind,Rain,Fire"
var myVar2 = a.join(', '); // myVar2的值变为"Wind, Rain, Fire"
var myVar3 = a.join(' + '); // myVar3的值变为"Wind + Rain + Fire"
var myVar4 = a.join(''); // myVar4的值变为"WindRainFire"
array.concat(value1, value2, ..., valueN)
concat 方法将创建一个新的数组,然后将调用它的对象(this 指向的对象)中的元素以及所有参数中的数组类型的参数中的元素以及非数组类型的参数本身按照顺序放入这个新数组,并返回该数组.
concat 方法并不修改调用它的对象(this 指向的对象) 和参数中的各个数组本身的值,而是将他们的每个元素拷贝一份放在组合成的新数组中.array.concat()
如果没有参数传入,就会返回array的一个浅复制.对新数组的任何操作都不会对原数组产生影响,反之亦然.原数组中的元素有两种被拷贝的方式:
var alpha = ["a", "b", "c"];
var numeric = [1, 2, 3];
// 组成新数组 ["a", "b", "c", 1, 2, 3]; 原数组 alpha 和 numeric 未被修改
var alphaNumeric = alpha.concat(numeric);
var num1 = [1, 2, 3];
var num2 = [4, 5, 6];
var num3 = [7, 8, 9];
// 组成新数组[1, 2, 3, 4, 5, 6, 7, 8, 9]; 原数组 num1, num2, num3 未被修改
var nums = num1.concat(num2, num3);
var alpha = ['a', 'b', 'c'];
// 组成新数组 ["a", "b", "c", 1, 2, 3], 原alpha数组未被修改
var alphaNumeric = alpha.concat(1, [2, 3]);
array.pop()
pop方法删除一个数组中的最后一个元素,并且把这个删除掉的元素返回给调用者。pop 被有意设计成具有通用性,该方法可以通过 call
或 apply
方法应用于一个类数组(array-like)对象上。
arr.push(element1, ..., elementN)
push方法push 方法把值添加到数组中。push 方法有意具有通用性。该方法和 call()
或 apply()
一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length不存在时,将会创建它。唯一的原生类数组(array-like)对象是 Strings
,尽管如此,它们并不适用该方法,因为字符串是不可改变的。
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];
Array.prototype.push.apply(vegetables, moreVegs);
console.log(vegetables); // ['parsnip', 'potato', 'celery', 'beetroot']
array.shift()
shift 方法移除索引为 0 的元素(即第一个元素),并返回被移除的元素,其他元素的索引值随之减 1。如果 length 属性的值为 0 (长度为 0),则返回 undefined
。 shift 方法并不局限于数组:该方法亦可通过 call
或 apply
作用于对象上。对于不包含 length 属性的对象,将添加一个值为 0 的 length 属性。
arr.unshift(element1, ..., elementN)
unshift 方法会在调用它的类数组(array-like)对象的开始位置插入给定的参数。unshift 特意被设计成具有通用性;这个方法能够通过 call
或 apply
方法作用于类似数组的对象上。不过对于没有 length 属性(代表从0开始的一系列连续的数字属性的最后一个)的对象,调用该方法可能没有任何意义。
var arr = [1, 2];
arr.unshift(0); //result of call is 3, the new array length
//arr is [0, 1, 2]
arr.unshift(-2, -1); // = 5
//arr is [-2, -1, 0, 1, 2]
arr.unshift( [-3] );
//arr is [[-3], -2, -1, 0, 1, 2]
模拟LIFO(last in first out)栈
function Stack () {
this._stack = []
}
Stack.prototype.next = function () {
return this._stack.pop()
}
Stack.prototype.add = function () {
return this._stack.push.apply(this._stack, arguments)
}
stack = new Stack()
stack.add(1,2,3)
stack.next()
// <- 3
模拟FIFO(first in first out)堆
function Queue () {
this._queue = []
}
Queue.prototype.next = function () {
return this._queue.shift()
}
Queue.prototype.add = function () {
return this._queue.unshift.apply(this._queue, arguments)
}
queue = new Queue()
queue.add(1,2,3)
queue.next()
// <- 1
array.map(callback[, thisArg])
map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值组合起来形成一个新数组。callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。
value
数组当前项的值index
当前的索引(或下标)array
数组本身values = [void 0, null, false, '']
values[7] = void 0
result = values.map(function(value, index, array){
console.log(value)
return value
})
// <- [undefined, null, false, '', undefined × 3, undefined]
// 通常使用parseInt时,只需要传递一个参数.但实际上,parseInt可以有两个参数.第二个参数是进制数.可以通过语句"alert(parseInt.length)===2"来验证.
[1, '2', '30', '9'].map(function (value) {
return parseInt(value, 10)
})
// 1, 2, 30, 9
[97, 119, 101, 115, 111, 109, 101].map(String.fromCharCode).join('')
// <- 'awesome'
// a commonly used pattern is mapping to new objects
items.map(function (item) {
return {
id: item.id,
name: computeName(item)
}
})
array.filter(callback[, thisArg])
filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或 等价于 true 的值 的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。 callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组.如果为 filter 提供一个 thisArg参数,则它会被作为 callback 被调用时的 this 值。否则,callback 的 this 值在非严格模式下将是全局对象,严格模式下为undefined。filter和上面的some,every,forEach,map 具有相同的特性;不会改变原数组。filter遍历的元素范围在第一次调用 callback 之前就已经确定了。在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。
[void 0, null, false, '', 1].filter(function (value) {
return value
})
// <- [1]
[void 0, null, false, '', 1].filter(function (value) {
return !value
})
// <- [void 0, null, false, '']
arr.sort([compareFunction])
sort()方法对数组的元素做原地的排序,并返回这个数组。 sort 可能不是稳定的。默认按照字符串的Unicode码位点(code point)排序。如果没有指明 compareFunction ,那么元素会被转换为字符串并按照万国码位点顺序排序。例如 "Cherry" 会被排列到 "banana" 之前。当对数字进行排序的时候, 9 会出现在 80 之后,因为他们会先被转换为字符串,而 "80" 比 "9" 要靠前。如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。记 a 和 b 是两个将要被比较的元素:
var stringArray = ["Blue", "Humpback", "Beluga"];
var numericStringArray = ["80", "9", "700"];
var numberArray = [40, 1, 5, 200];
var mixedNumericArray = ["80", "9", "700", 40, 1, 5, 200];
function compareNumbers(a, b)
{
return a - b;
}
console.log('stringArray:', stringArray.join());
console.log('Sorted:', stringArray.sort());
console.log('numberArray:', numberArray.join());
console.log('Sorted without a compare function:', numberArray.sort());
console.log('Sorted with compareNumbers:', numberArray.sort(compareNumbers));
console.log('numericStringArray:', numericStringArray.join());
console.log('Sorted without a compare function:', numericStringArray.sort());
console.log('Sorted with compareNumbers:', numericStringArray.sort(compareNumbers));
console.log('mixedNumericArray:', mixedNumericArray.join());
console.log('Sorted without a compare function:', mixedNumericArray.sort());
console.log('Sorted with compareNumbers:', mixedNumericArray.sort(compareNumbers));
对非 ASCII 字符排序
var items = ['réservé', 'premier', 'cliché', 'communiqué', 'café', 'adieu'];
items.sort(function (a, b) {
return a.localeCompare(b);
});
// items is ['adieu', 'café', 'cliché', 'communiqué', 'premier', 'réservé']
使用映射改善排序
// 需要被排序的数组
var list = ['Delta', 'alpha', 'CHARLIE', 'bravo']
// 对需要排序的数字和位置的临时存储
var mapped = list.map(function(el, i) {
return { index: i, value: el.toLowerCase() };
})
// 按照多个值排序数组
mapped.sort(function(a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
});
// 根据索引得到排序的结果
var result = mapped.map(function(el){
return list[el.index];
});
array.reduce(callback,[initialValue])
reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。回调函数第一次执行时,previousValue 和 currentValue 可以是一个值,如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 等于 initialValue ,并且currentValue 等于数组中的第一个值;如果initialValue 未被提供,那么previousValue 等于数组中的第一个值,currentValue等于数组中的第二个值。如果数组为空并且没有提供initialValue, 会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。
Array.prototype.sum = function () {
return this.reduce(function (partial, value) {
return partial + value
}, 0)
};
[3,4,5,6,10].sum()
// <- 28
function concat (input) {
return input.reduce(function (partial, value) {
if (partial) {
partial += ', '
}
return partial + value.name
}, '')
}
concat([
{ name: 'George' },
{ name: 'Sam' },
{ name: 'Pear' }
])
// <- 'George, Sam, Pear'
arr.reduceRight(callback[, initialValue])
reduceRight() 方法接受一个函数作为累加器(accumulator),让每个值(从右到左,亦即从尾到头)缩减为一个值。(与 reduce() 的执行方向相反)
var total = [0, 1, 2, 3].reduceRight(function(a, b) {
return a + b;
});
// total == 6
var flattened = [[0, 1], [2, 3], [4, 5]].reduceRight(function(a, b) {
return a.concat(b);
}, []);
// flattened is [4, 5, 2, 3, 0, 1]
array.slice([begin[,end]])
slice不修改原数组,只会返回一个包含了原数组中提取的部分元素的一个新数组。原数组的元素会按照下述规则拷贝:
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
function list() {
return slice(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// <- ['a', 'b']
Array.prototype.concat.call({ 0: 'a', 1: 'b', length: 2 })
// <- [{ 0: 'a', 1: 'b', length: 2 }]
function format (text, bold) {
if (bold) {
text = '<b>' + text + '</b>'
}
var values = Array.prototype.slice.call(arguments, 2)
values.forEach(function (value) {
text = text.replace('%s', value)
})
return text
}
format('some%sthing%s %s', true, 'some', 'other', 'things')
// <- <b>somesomethingother things</b>
array.splice(start, deleteCount[, item1[, item2[, ...]]])
splice() 方法用新元素替换旧元素,以此修改数组的内容。如果添加进数组的元素个数不等于被删除的元素个数,数组的长度会发生相应的改变。
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13]
var spliced = source.splice(9)
spliced.forEach(function (value) {
console.log('removed', value)
})
// <- removed 10
// <- removed 11
// <- removed 12
// <- removed 13
console.log(source)
// <- [1, 2, 3, 8, 8, 8, 8, 8, 9]
arr.indexOf(searchElement[, fromIndex = 0])
indexOf()方法返回给定元素能找在数组中找到的第一个索引值,否则返回-1。 indexOf 使用strict equality (无论是 ===, 还是 triple-equals操作符都基于同样的方法)进行判断 searchElement与数组中包含的元素之间的关系。searchElement:要查找的元素,fromIndex:开始查找的位置。
var a = { foo: 'bar' }
var b = [a, 2]
console.log(b.indexOf(1))
// <- -1
console.log(b.indexOf({ foo: 'bar' }))
// <- -1
console.log(b.indexOf(a))
// <- 0
console.log(b.indexOf(a, 1))
// <- -1
b.indexOf(2, 1)
// <- 1
array.reverse
reverse() 方法颠倒数组中元素的位置。第一个元素会成为最后一个,最后一个会成为第一个。简称逆序
reverse 方法颠倒数组中元素的位置,并返回该数组的引用。
var a = [1, 2, 3, 4]
a.reverse()
// [4, 3, 2, 1]
最后分享一段有趣的代码,在你的浏览器控制台中运行,页面中各层的HTML就会被不同的颜色标记出来。
[].forEach.call($$("*"),function(a){
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
})
我们都知道函数在JavaScript中的地位是非常重要的,函数式编程是这门语言的精髓,本文不会在这里针对函数去展开,后面我会详细介绍JavaScript的函数式编程的一些概念,这里主要是收集一些我们平时常用到的函数,如果能熟练应用的话会对你的编程速度有很大的提高。希望能帮助到更多的人。
1.将类数组对象转换为真数组
function arrayify(a){
return [].slice.call(a);
}
2.判断是否移动设备访问
//first
var ua = navigator.userAgent.toLowerCase(),
isMobile = ua.search( /(iphone)|(ipod)|(android)/ ) === -1; //false是移动设备,true不是
//second
function isMobileUserAgent(){
var ua = navigator.userAgent.toLowerCase();
return (/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(ua));
}
3.获取当前路径
var currentUrl = document.location.toString().toLowerCase()||this.href.toString().toLowerCase();
//这种情况其实就是if else 的一种简写表示当对象存在为true的时候给它后面的值 如果对象不存在为false的时候给它前面的默认值
4.字符串长度截取
function cutstr(str, len) {
var temp,
icount = 0,
patrn = /[^\x00-\xff]/,
strre = "";
for (var i = 0; i < str.length; i++) {
if (icount < len - 1) {
temp = str.substr(i, 1);
if (patrn.exec(temp) == null) {
icount = icount + 1
} else {
icount = icount + 2
}
strre += temp
} else {
break;
}
}
return strre + "..."
}
5.替换全部
String.prototype.replaceAll = function(s1, s2) {
return this.replace(new RegExp(s1, "gm"), s2)
}
6.清除空格
String.prototype.trim = function() {
var reExtraSpace = /^\s*(.*?)\s+$/;
return this.replace(reExtraSpace, "$1")
}
function ltrim(s){ return s.replace( /^(\s*| *)/, ""); } //清除左空格
function rtrim(s){ return s.replace( /(\s*| *)$/, ""); } //清除右空格
7.判断是否以某个字符串开头,结束
String.prototype.startWith = function (s) {
return this.indexOf(s) == 0
}//开头
String.prototype.endWith = function (s) {
var d = this.length - s.length;
return (d >= 0 && this.lastIndexOf(s) == d)
} //结束
8.转义html标签
function HtmlEncode(text) {
return text.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>')
}
9.判断是否为数字类型
function isDigit(value) {
var patrn = /^[0-9]*$/;
if (patrn.exec(value) == null || value == "") {
return false
} else {
return true
}
}
10.设置cookie值
function setCookie(name, value, Hours) {
var d = new Date();
var offset = 8;
var utc = d.getTime() + (d.getTimezoneOffset() * 60000);
var nd = utc + (3600000 * offset);
var exp = new Date(nd);
exp.setTime(exp.getTime() + Hours * 60 * 60 * 1000);
document.cookie = name + "=" + escape(value) + ";path=/;expires=" + exp.toGMTString() + ";domain=www.163.com;"
}
11.获取cookie值
function getCookie(name) {
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
if (arr != null) return unescape(arr[2]);
return null
}
12.加载样式文件
function LoadStyle(url) {
try {
document.createStyleSheet(url)
} catch(e) {
var cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = url;
var head = document.getElementsByTagName('head')[0];
head.appendChild(cssLink)
}
}
13.返回脚本
function evalscript(s) {
if(s.indexOf('<script') == -1) return s;
var p = /<script[^\>]*?>([^\x00]*?)<\/script>/ig;
var arr = [];
while(arr = p.exec(s)) {
var p1 = /<script[^\>]*?src=\"([^\>]*?)\"[^\>]*?(reload=\"1\")?(?:charset=\"([\w\-]+?)\")?><\/script>/i;
var arr1 = [];
arr1 = p1.exec(arr[0]);
if(arr1) {
appendscript(arr1[1], '', arr1[2], arr1[3]);
} else {
p1 = /<script(.*?)>([^\x00]+?)<\/script>/i;
arr1 = p1.exec(arr[0]);
appendscript('', arr1[2], arr1[1].indexOf('reload=') != -1);
}
}
return s;
}
14.清除脚本
function stripscript(s) {
return s.replace(/<script.*?>.*?<\/script>/ig, '');
}
15.动态加载脚本
function appendscript(src, text, reload, charset) {
var id = hash(src + text);
if(!reload && in_array(id, evalscripts)) return;
if(reload && $(id)) {
$(id).parentNode.removeChild($(id));
}
evalscripts.push(id);
var scriptNode = document.createElement("script");
scriptNode.type = "text/javascript";
scriptNode.id = id;
scriptNode.charset = charset ? charset : (BROWSER.firefox ? document.characterSet : document.charset);
try {
if(src) {
scriptNode.src = src;
scriptNode.onloadDone = false;
scriptNode.onload = function () {
scriptNode.onloadDone = true;
JSLOADED[src] = 1;
};
scriptNode.onreadystatechange = function () {
if((scriptNode.readyState == 'loaded' || scriptNode.readyState == 'complete') && !scriptNode.onloadDone) {
scriptNode.onloadDone = true;
JSLOADED[src] = 1;
}
};
} else if(text){
scriptNode.text = text;
}
document.getElementsByTagName('head')[0].appendChild(scriptNode);
} catch(e) {}
}
16.返回按ID检索的元素对象
function $(id) {
return !id ? null : document.getElementById(id);
}
17.跨浏览器绑定事件
function addEventSamp(obj,evt,fn){
if(!oTarget){return;}
if (obj.addEventListener) {
obj.addEventListener(evt, fn, false);
}else if(obj.attachEvent){
obj.attachEvent('on'+evt,fn);
}else{
oTarget["on" + sEvtType] = fn;
}
}
18.跨浏览器删除事件
function delEvt(obj,evt,fn){
if(!obj){return;}
if(obj.addEventListener){
obj.addEventListener(evt,fn,false);
}else if(oTarget.attachEvent){
obj.attachEvent("on" + evt,fn);
}else{
obj["on" + evt] = fn;
}
}
19.为元素添加on方法
Element.prototype.on = Element.prototype.addEventListener;
NodeList.prototype.on = function (event, fn) {、
[]['forEach'].call(this, function (el) {
el.on(event, fn);
});
return this;
};
20.为元素添加trigger方法
Element.prototype.trigger = function (type, data) {
var event = document.createEvent('HTMLEvents');
event.initEvent(type, true, true);
event.data = data || {};
event.eventName = type;
event.target = this;
this.dispatchEvent(event);
return this;
};
NodeList.prototype.trigger = function (event) {
[]['forEach'].call(this, function (el) {
el['trigger'](event);
});
return this;
};
21.getElementsByClassName
function getElementsByClassName(name) {
var tags = document.getElementsByTagName('*') || document.all;
var els = [];
for (var i = 0; i < tags.length; i++) {
if (tags.className) {
var cs = tags.className.split(' ');
for (var j = 0; j < cs.length; j++) {
if (name == cs[j]) {
els.push(tags);
break
}
}
}
}
return els
}
22.获取页面高度
function getPageHeight(){
var g = document, a = g.body, f = g.documentElement, d = g.compatMode == "BackCompat"
? a
: g.documentElement;
return Math.max(f.scrollHeight, a.scrollHeight, d.clientHeight);
}
23.获取页面scrollLeft
function getPageScrollLeft(){
var a = document;
return a.documentElement.scrollLeft || a.body.scrollLeft;
}
24.获取页面可视宽度
function getPageViewWidth(){
var d = document, a = d.compatMode == "BackCompat"
? d.body
: d.documentElement;
return a.clientWidth;
}
25.获取页面宽度
function getPageWidth(){
var g = document, a = g.body, f = g.documentElement, d = g.compatMode == "BackCompat"
? a
: g.documentElement;
return Math.max(f.scrollWidth, a.scrollWidth, d.clientWidth);
}
26.获取页面scrollTop
function getPageScrollTop(){
var a = document;
return a.documentElement.scrollTop || a.body.scrollTop;
}
27.获取页面可视高度
function getPageViewHeight() {
var d = document, a = d.compatMode == "BackCompat"
? d.body
: d.documentElement;
return a.clientHeight;
}
28.获取窗体可见范围的宽与高
function getViewSize(){
var de=document.documentElement;
var db=document.body;
var viewW=de.clientWidth==0 ? db.clientWidth : de.clientWidth;
var viewH=de.clientHeight==0 ? db.clientHeight : de.clientHeight;
return Array(viewW ,viewH);
}
29.去掉url前缀
function removeUrlPrefix(a){
a=a.replace(/:/g,":").replace(/./g,".").replace(///g,"/");
while(trim(a).toLowerCase().indexOf("http://")==0){
a=trim(a.replace(/http:\/\//i,""));
}
return a;
}
30.断鼠标是否移出事件
function isMouseOut(e, handler) {
if (e.type !== 'mouseout') {
return false;
}
var reltg = e.relatedTarget ? e.relatedTarget : e.type === 'mouseout' ? e.toElement : e.fromElement;
while (reltg && reltg !== handler) {
reltg = reltg.parentNode;
}
return (reltg !== handler);
}
31.获得URL中GET参数值
function get_get(){
querystr = window.location.href.split("?")
if(querystr[1]){
GETs = querystr[1].split("&");
GET = [];
for(i=0;i<GETs.length;i++){
tmp_arr = GETs.split("=")
key=tmp_arr[0]
GET[key] = tmp_arr[1]
}
}
return querystr[1];
}
32.清除相同的数组
String.prototype.unique=function(){
var x=this.split(/[\r\n]+/);
var y='';
for(var i=0;i<x.length;i++){
if(!new RegExp("^"+x.replace(/([^\w])/ig,"\\$1")+"$","igm").test(y)){
y+=x+"\r\n"
}
}
return y
};
33.按字母排序,对每行进行数组排序
function SetSort(){
var text=K1.value.split(/[\r\n]/).sort().join("\r\n");//顺序
var test=K1.value.split(/[\r\n]/).sort().reverse().join("\r\n");//反序
K1.value=K1.value!=text?text:test;
}
34.字符串反序
function isReverse(text){
return text.split('').reverse().join('');
}
35.清除html代码中的脚本
function clear_script(){
K1.value=K1.value.replace(/<script.*?>[\s\S]*?<\/script>|\s+on[a-zA-Z]{3,16}\s?=\s?"[\s\S]*?"|\s+on[a-zA-Z]{3,16}\s?=\s?'[\s\S]*?'|\s+on[a-zA-Z]{3,16}\s?=[^ >]+/ig,"");
}
动态执行JavaScript脚本
function javascript(){
try{
eval(K1.value);
}catch(e){
alert(e.message);
}
}
36.实现base64解码
function base64_decode(data){
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,ac = 0,dec = "",tmp_arr = [];
if (!data) { return data; }
data += '';
do {
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < data.length);
dec = tmp_arr.join('');
dec = utf8_decode(dec);
return dec;
}
37.实现utf8解码
function utf8_decode(str_data){
var tmp_arr = [],i = 0,ac = 0,c1 = 0,c2 = 0,c3 = 0;str_data += '';
while (i < str_data.length) {
c1 = str_data.charCodeAt(i);
if (c1 < 128) {
tmp_arr[ac++] = String.fromCharCode(c1);
i++;
} else if (c1 > 191 && c1 < 224) {
c2 = str_data.charCodeAt(i + 1);
tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = str_data.charCodeAt(i + 1);
c3 = str_data.charCodeAt(i + 2);
tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return tmp_arr.join('');
}
38.随机数时间戳
function uniqueId(){
var a=Math.random,b=parseInt;
return Number(new Date()).toString()+b(10*a())+b(10*a())+b(10*a());
}
39.获取网页被卷去的位置
function getScrollXY() {
return document.body.scrollTop ? {
x: document.body.scrollLeft,
y: document.body.scrollTop
}: {
x: document.documentElement.scrollLeft,
y: document.documentElement.scrollTop
}
}
40.检验URL链接是否有效
function getUrlState(URL){
var xmlhttp = new ActiveXObject("microsoft.xmlhttp");
xmlhttp.Open("GET",URL, false);
try{
xmlhttp.Send();
}catch(e){
}finally{
var result = xmlhttp.responseText;
if(result){
if(xmlhttp.Status==200){
return(true);
}else{
return(false);
}
}else{
return(false);
}
}
}
41.获取URL中的参数
var GLOBLE_PARAMS = (function () {
var args = new Object();
var query = location.search.substring(1);
var pairs = query.split("&"); // Break at ampersand
for(var i = 0; i < pairs.length; i++) {
var pos = pairs[i].indexOf('=');
if (pos == -1) continue;
var argname = pairs[i].substring(0,pos);
var value = pairs[i].substring(pos+1);
value = decodeURIComponent(value);
args[argname] = value;
}
return args;
})();
//[1, [2, [ [3, 4], 5], 6]] => [1, 2, 3, 4, 5, 6]
function flatten(arr){
var i,ret=[], len = arr.length;
for(i=0;i<len;i++){
if(Array.isArray(arr[i])){
ret = ret.concat(flatten(arr[i]))
}else{
ret.push(arr[i])
}
}
return ret
}
/**
* Camelize a string, cutting the string by multiple separators like
* hyphens, underscores and spaces.
*
* @param {text} string Text to camelize
* @return string Camelized text
*/
function camelize(text) {
return text.replace(/^([A-Z])|[\s-_]+(\w)/g, function(match, p1, p2, offset) {
if (p2) return p2.toUpperCase();
return p1.toLowerCase();
});
}
// someDatabaseFieldName
console.log(camelize("some_database_field_name"));
// someLabelThatNeedsToBeCamelized
console.log(camelize("Some label that needs to be camelized"));
// someJavascriptProperty
console.log(camelize("some-javascript-property"));
// someMixedStringWithSpacesUnderscoresAndHyphens
console.log(camelize("some-mixed_string with spaces_underscores-and-hyphens"));
/**
* Decamelizes a string with/without a custom separator (underscore by default).
*
* @param str String in camelcase
* @param separator Separator for the new decamelized string.
*/
function decamelize(str, separator){
separator = typeof separator === 'undefined' ? '_' : separator;
return str
.replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
.toLowerCase();
}
// some database field name (separate with an empty space)
console.log(decamelize("someDatabaseFieldName", " "));
// some-label-that-needs-to-be-camelized (separate with an hyphen)
console.log(decamelize("someLabelThatNeedsToBeCamelized", "-"));
// some_javascript_property (separate with underscore)
console.log(decamelize("someJavascriptProperty", "_"));
fork 到自己的仓库
git clone 到本地
上游建立连接
git remote add upstream 开源项目地址
创建开发分支 (非必须)
git checkout -b dev
修改提交代码
git status
git add .
git commit -m
git push origin branch
同步代码三部曲
git fetch upstream
git rebase upstream/master
git push origin master
提交pr
去自己github仓库对应fork的项目下new pull request
属性 | 描述 |
---|---|
onabort | 在退出时运行的脚本 |
oncanplay | 当文件就绪可以开始播放时运行的脚本(缓冲已足够开始时) |
oncanplaythrough | 当媒介能够无需因缓冲而停止即可播放至结尾时运行的脚本 |
ondurationchange | 当媒介长度改变时运行的脚本 |
onemptied | 当发生故障并且文件突然不可用时运行的脚本(比如连接意外断开时) |
onended | 当媒介已到达结尾时运行的脚本(可发送类似“感谢观看”之类的消息) |
onerror | 当在文件加载期间发生错误时运行的脚本 |
onloadeddata | 当媒介数据已加载时运行的脚本 |
onloadedmetadata | 当元数据(比如分辨率和时长)被加载时运行的脚本 |
onloadstart | 在文件开始加载且未实际加载任何数据前运行的脚本 |
onpause | 当媒介被用户或程序暂停时运行的脚本 |
onplay | 当媒介已就绪可以开始播放时运行的脚本 |
onplaying | 当媒介已开始播放时运行的脚本 |
onprogress | 当浏览器正在获取媒介数据时运行的脚本 |
onratechange | 每当回放速率改变时运行的脚本(比如当用户切换到慢动作或快进模式) |
onreadystatechange | 每当就绪状态改变时运行的脚本(就绪状态监测媒介数据的状态) |
onseeked | 当 seeking 属性设置为 false(指示定位已结束)时运行的脚本 |
onseeking | 当 seeking 属性设置为 true(指示定位是活动的)时运行的脚本 |
onstalled | 在浏览器不论何种原因未能取回媒介数据时运行的脚本 |
onsuspend | 在媒介数据完全加载之前不论何种原因终止取回媒介数据时运行的脚本 |
ontimeupdate | 当播放位置改变时(比如当用户快进到媒介中一个不同的位置时)运行的脚本 |
onvolumechange | 每当音量改变时(包括将音量设置为静音)时运行的脚本 |
onwaiting | 当媒介已停止播放但打算继续播放时(比如当媒介暂停已缓冲更多数据)运行脚本 |
Event | PC | iOS | Android |
---|---|---|---|
loadstart | 文件加载,video初始化,未加载任何数据 | 与PC一致 | 一致 |
stalled | 视频没有播放,没有取回任何媒介数据:一般是由于网络状况不佳,导致视频下载中断 | 一致 | 可能在play()事件触发前 |
play | play()事件触发,状态是开始播放,但视频并未真正开始播放 | 一致 | 一致 |
waiting | play()事件触发后,等待数据 | 一致 | 一致 |
durationchange | 获取到视频长度,duration属性能获得真实视频长度 | 一致 | 可能在play()事件触发前,可能没有获取到真实的视频长度:可能触发多次, 只有最后一次才能获取到真实的duration,之前的值有可能为0或者1 |
loadedmetadata | play()事件触发后,获取到元数据 | 一致 | play()事件触发前,没有获取到真实的元数据 |
loadeddata | play()事件触发后,获取到媒介数据 | 一致 | play()事件触发前,没有获取到真实的媒介数据 |
canplay | 可以播放,但视频可能还未真正开始播放,并且中途可能因为加载而暂停 | 一致 | 一致 |
playing | 视频开始播放 | 一致 | 可能还未真正开始播放,并且可能还未获取到视频长度 |
canplaythrough | 视频开始播放后,可以流畅播放 | 一致 | 数据可能还没有开始加载,视频可能还未开始播放, 视频仍然会卡住 |
timeupdate | 视频播放后,更新播放进度, 会有明确的进度变化,可以获取到currentTime | 一致 | 第一次可能会有误差,如果 timeupdate事件的currentTime发生变化,代表视频一定开始播放 |
progress | 视频播放后,持续下载, 可以获取到当前的缓存buffer,并且全部下载完毕后不再触发 | 一致 | 第一次可能会有误差, 全部下载完毕后依然继续触发 |
suspend | 缓冲中,视频可能卡顿也可能在流畅播放中,全部缓存完毕后不再触发。视频还未真实播放前,pause()事件会触发suspend | 一致 | 一致 |
pause | 可能是响应pause()事件暂停,或者是切出页面自动暂停 | 一致 | 一致 |
seeking | 拖动进度条时,寻找播放位置。或者播放完毕,寻找下一个视频 | 一致 | 一致 |
seeked | 拖动进度条时,定位到播放位置。或者开始播放下一个视频,或者是从头开始循环播放 | 一致 | 一致 |
error | 错误,无法定位错误原因,无法通过paly()事件继续播放 | 一致 | 一致 |
JSZip是一个JavaScript库 可以很方便的用来读写.zip文件同时可以用在服务器端
let zip = new JSZip();
zip.file("Hello.txt", "Hello World\n");
let img = zip.folder("images");
img.file("smile.gif", imgData, { base64: true });
zip.generateAsync({ type: "blob" }).then(function(content) {
// see FileSaver.js
saveAs(content, "example.zip");
});
fetch("test.zip")
.then(response => response.arrayBuffer())
.then(JSZip.loadAsync)
.then(zip => {
// use file index for file
zip
.file("test.json")
.async("uint8array")
.then(u => console.log(u));
});
FFmpeg是一套开源多媒体视频处理工具集,在音视频流媒体领域有着举足轻重的作用,详细可以看维基百科FFmpeg,本文主要介绍其命令行工具ffmpeg,整理出来一些常用命令方便查找使用。
通过ffmpeg --help
可以看到ffmpeg常见的命令大概分为6个部分:
命令使用:
ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件]
参数选项详细见官网
-L license
-h 帮助
-fromats 显示可用的格式,编解码的,协议的。。。
-f fmt 强迫采用格式fmt
-I filename 输入文件
-y 覆盖输出文件
-t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持
-ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持
-title string 设置标题
-author string 设置作者
-copyright string 设置版权
-comment string 设置评论
-target type 设置目标文件类型(vcd,svcd,dvd) 所有的格式选项(比特率,编解码以及缓冲区大小)自动设置,只需要输入如下的就可以了:
ffmpeg -i myfile.avi -target vcd /tmp/vcd.mpg
-hq 激活高质量设置
-itsoffset offset 设置以秒为基准的时间偏移,该选项影响所有后面的输入文件。该偏移被加到输入文件的时戳,定义一个正偏移意味着相应的流被延迟了 offset秒。 [-]hh:mm:ss[.xxx]的格式也支持
-b bitrate 设置比特率,缺省200kb/s
-r fps 设置帧频 缺省25
-s size 设置帧大小 格式为WXH 缺省160X128.下面的简写也可以直接使用:
Sqcif 128X96 qcif 176X144 cif 252X288 4cif 704X576
-aspect aspect 设置横纵比 4:3 16:9 或 1.3333 1.7777
-croptop size 设置顶部切除带大小 像素单位
-cropbottom size –cropleft size –cropright size
-padtop size 设置顶部补齐的大小 像素单位
-padbottom size –padleft size –padright size –padcolor color 设置补齐条颜色(hex,6个16进制的数,红:绿:兰排列,比如 000000代表黑色)
-vn 不做视频记录
-bt tolerance 设置视频码率容忍度kbit/s
-maxrate bitrate设置最大视频码率容忍度
-minrate bitreate 设置最小视频码率容忍度
-bufsize size 设置码率控制缓冲区大小
-vcodec codec 强制使用codec编解码方式。如果用copy表示原始编解码数据必须被拷贝。
-sameq 使用同样视频质量作为源(VBR)
-pass n 选择处理遍数(1或者2)。两遍编码非常有用。第一遍生成统计信息,第二遍生成精确的请求的码率
-passlogfile file 选择两遍的纪录文件名为file
-intra 仅适用帧内编码
-qscale q 使用固定的视频量化标度(VBR)
-qmin q 最小视频量化标度(VBR)
-qmax q 最大视频量化标度(VBR)
-qdiff q 量化标度间最大偏差 (VBR)
-qblur blur 视频量化标度柔化(VBR)
-qcomp compression 视频量化标度压缩(VBR)
-rc_init_cplx complexity 一遍编码的初始复杂度
-b_qfactor factor 在p和b帧间的qp因子
-i_qfactor factor 在p和i帧间的qp因子
-b_qoffset offset 在p和b帧间的qp偏差
-i_qoffset offset 在p和i帧间的qp偏差
-rc_eq equation 设置码率控制方程 默认tex^qComp
-rc_override override 特定间隔下的速率控制重载
-me method 设置运动估计的方法 可用方法有 zero phods log x1 epzs(缺省) full
-dct_algo algo 设置dct的算法 可用的有 0 FF_DCT_AUTO 缺省的DCT 1 FF_DCT_FASTINT 2 FF_DCT_INT 3 FF_DCT_MMX 4 FF_DCT_MLIB 5 FF_DCT_ALTIVEC
-idct_algo algo 设置idct算法。可用的有 0 FF_IDCT_AUTO 缺省的IDCT 1 FF_IDCT_INT 2 FF_IDCT_SIMPLE 3 FF_IDCT_SIMPLEMMX 4 FF_IDCT_LIBMPEG2MMX 5 FF_IDCT_PS2 6 FF_IDCT_MLIB 7 FF_IDCT_ARM 8 FF_IDCT_ALTIVEC 9 FF_IDCT_SH4 10 FF_IDCT_SIMPLEARM
-er n 设置错误残留为n 1 FF_ER_CAREFULL 缺省 2 FF_ER_COMPLIANT 3 FF_ER_AGGRESSIVE 4 FF_ER_VERY_AGGRESSIVE
-ec bit_mask 设置错误掩蔽为bit_mask,该值为如下值的位掩码 1 FF_EC_GUESS_MVS (default=enabled) 2 FF_EC_DEBLOCK (default=enabled)
-bf frames 使用frames B 帧,支持mpeg1,mpeg2,mpeg4
-mbd mode 宏块决策 0 FF_MB_DECISION_SIMPLE 使用mb_cmp 1 FF_MB_DECISION_BITS 2 FF_MB_DECISION_RD
-4mv 使用4个运动矢量 仅用于mpeg4
-part 使用数据划分 仅用于mpeg4
-bug param 绕过没有被自动监测到编码器的问题
-strict strictness 跟标准的严格性
-aic 使能高级帧内编码 h263+
-umv 使能无限运动矢量 h263+
-deinterlace 不采用交织方法
-interlace 强迫交织法编码仅对mpeg2和mpeg4有效。当你的输入是交织的并且你想要保持交织以最小图像损失的时候采用该选项。可选的方法是不交织,但是损失更大
-psnr 计算压缩帧的psnr
-vstats 输出视频编码统计到vstats_hhmmss.log
-vhook module 插入视频处理模块 module 包括了模块名和参数,用空格分开
-ar freq 设置音频采样率
-ac channels 设置通道 缺省为1
-an 不使能音频纪录
-acodec codec 使用codec编解码
-vd device 设置视频捕获设备。比如/dev/video0
-vc channel 设置视频捕获通道 DV1394专用
-tvstd standard 设置电视标准 NTSC PAL(SECAM)
-dv1394 设置DV1394捕获
-av device 设置音频设备 比如/dev/dsp
-map file:stream 设置输入流映射
-debug 打印特定调试信息
-benchmark 为基准测试加入时间
-hex 倾倒每一个输入包
-bitexact 仅使用位精确算法 用于编解码测试
-ps size 设置包大小,以bits为单位
-re 以本地帧频读数据,主要用于模拟捕获设备
-loop 循环输入流。只工作于图像流,用于ffserver测试
ffmpeg -i input_file -vcodec copy -an output_file_video //分离视频流
ffmpeg -i input_file -acodec copy -vn output_file_audio //分离音频流
ffmpeg –i test.mp4 –vcodec copy –an –f m4v test.264
ffmpeg –i test.avi –vcodec copy –an –f m4v test.264
ffmpeg -i test.mp4 -vcoder h264 -s 352*278 -an -f m4v test.264 //转码为码流原始文件
ffmpeg -i test.mp4 -vcoder h264 -bf 0 -g 25 -s 352-278 -an -f m4v test.264 //转码为码流原始文件
ffmpeg -i test.avi -vcoder mpeg4 -vtag xvid -qsame test_xvid.avi //转码为封装文件 -bf B帧数目控制, -g 关键帧间隔控制, -s 分辨率控制
ffmpeg -i video_file -i audio_file -vcoder copy -acodec copy output_file
ffmpeg -i test.avi -r 1 -f image2 image.jpeg //视频截图
ffmpeg -i input.avi -ss 0:1:30 -t 0:0:20 -vcoder copy -acoder copy output.avi //剪切视频 -r 提取图像频率, -ss 开始时间, -t 持续时间
ffmpeg -i input.avi -r 24 output.avi // 强制把输出视频文件帧率改为 24 fps:-r 帧率
ffmpeg -i input.mp4 -b:v 2000k -bufsize 2000k -maxrate 2500k output.mp4//设置码率阈值
ffmpeg -i input.mp4 -i logo.png -filter_complex overlay=0:H-h output.mp4// 添加右下角
ffmpeg -i output.mp4 -filter_complex "delogo=x=25:y=25:w=150:h=50" delogo.mp4
ffmpeg -i input_file -y -f image2 -t 0.001 -s 352x240 output.jpg //截取一张352x240尺寸大小,格式为jpg的图片
ffmpeg -i input_file -vframes 30 -y -f gif output.gif
ffmpeg -i input_file -y -f mjpeg -ss 8 -t 0.001 -s 320x240 output.jpg
ffmpeg -i out.mp4 -f image2 -vf fps=fps=1 out%d.png
ffmpeg -i out.mp4 -f image2 -vf fps=fps=1/20 out%d.png //每隔20秒截一张图
ffmpeg -i out.mp4 -t 10 -pix_fmt rgb24 out.gif
ffmpeg -ss 3 -t 5 -i input.mp4 -s 480*270 -f gif out.gif //视频截选指定长度的内容生成GIF图片
ffmpeg -i out.mp4 out%4d.png
ffmpeg -f image2 -i out%4d.png -r 25 video.mp4
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -f hls -hls_time 20 -hls_list_size 0 -hls_wrap 0 output.m3u8
ffmpeg -i rtsp://hostname/test -vcoder copy out.avi
ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file
ffmpeg -i input_file -vcodec rawvideo -an output_file_yuv
ffmpeg -y -i chrome.mp4 -vf 'fps=1/5,scale=88x50,tile=100x1:padding=0:margin=0' -an -vsync 0 sprites.png
# 启动
systemctl start firewalld
# 查看状态
systemctl status firewalld
# 停止关闭
systemctl disable firewalld
systemctl stop firewalld
# 把一个源地址加入白名单,以便允许来自这个源地址的所有连接
# 这个在集群中使用常见
# 设置后利用firewall-cmd --reload更新防火墙规则
firewall-cmd --add-rich-rule 'rule family="ipv4" source address="192.168.1.215" accept' --permanent
firewall-cmd --reload
# 特定域内的用户通过ssh可以连接,24标识255.255.255.0
firewall-cmd --remove-service=ssh --permanent
firewall-cmd --add-rich-rule 'rule family=ipv4 source address=172.16.30.0/24
service name=ssh accept' --permanent
firewall-cmd --reload
firewall-cmd --list-all
# 将一个用户加入白名单
firewall-cmd --add-lockdown-whitelist-user=hadoop --permanent
firewall-cmd --reload
# 将用户id从白名单中去掉
firewall-cmd --remove-lockdown-whitelist-uid=uid
firewall-cmd --reload
# 查看所有打开的端口:
firewall-cmd --list-ports
# 在某个区域打开端口
firewall-cmd --zone=public --add-port=8080/tcp --permanent
# 关闭端口
firewall-cmd --remove-port=465/tcp
# 打开服务,参见/etc/firewalld 目录下services文件夹中的服务,可以配置
firewall-cmd --permanent --zone=public --add-service=samba
firewall-cmd --add-service=http --permanent
firewall-cmd --reload
# 关闭服务
firewall-cmd --zone=public --remove-service=samba
firewall-cmd --reload
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式)/函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | 单目运算符 | ||
-- | 自减运算符 | --变量名/变量名-- | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式%整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
4 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!=表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
醋坛酸味灌
味落跳福豆
共44个运算符:
醋-初等,4个: ( ) [ ] -> 指向结构体成员 . 结构体成员
坛-单目,9个: ! ~ ++ -- -负号 (类型) *指针 &取地址 sizeof长度
酸-算术,5个: * / % + -减
味-位移,2个: << >>
灌-关系,6个: < <= > >= == 等于 != 不等于
味-位逻,3个: & 按位与 ^ 按位异或 | 按位或
落-逻辑,2个: && 逻辑与 || 逻辑或
跳-条件,1个,三目: ? :
福-赋值,11个: = += -= *= /= %= >>= <<= &= ^= |=
豆-逗号,1个: ,
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
normal mode
)insert mode
)visual mode
)excute mode
)下面整理了常用的快捷键和记忆方法(结合英文的记忆方法法)
按键 | 效果 | 记忆方法 |
---|---|---|
h j k l |
向左/下/上/右移动 | ←↑↓→ |
w |
移动到下个单词开头 | word |
W |
移动到下个单词开头(包含标点) | Word |
e |
移动到下个单词结尾 | end |
E |
移动到下个单词结尾(单词含标点) | End |
b |
移动到上个单词开头 | back |
B |
移动到上个单词结尾(单词含标点) | Back |
0 |
移动到行首 | hard ⇤ |
^ |
移动到行首的非空白符 | soft ⇤ |
$ |
移动到行尾 | ⇥ |
H |
当前屏幕的第一行 | High |
M |
当前屏幕的中间 | Middle |
L |
当前页的的最后一行 | Low |
gg |
移动到文件第一行 | goto line1 |
G |
移动到文件最后一行 | Goto EOF |
5G |
移动到第五行 | -Goto line5 |
按键 | 效果 | 记忆方法 |
---|---|---|
f{char} /F{char} |
在行内向下/向上查找字符{char} (光标在字符上) | find/Find |
t{char} /T{char} |
在行内向下/向上查找字符{char}(光标在字符前面) | till /Till |
; /, |
跟f /F /t /T 结合使用,,跟查找顺序相同/相反的下一个匹配项 |
- |
/pattern |
文档向下查找匹配项 | - |
?pattern |
文档内向上查匹配项 | - |
n /N |
跟/ 和? 结合使用,跟查找顺序相同/相反的下一个匹配项 |
next/Next |
按键 | 效果 | 记忆方法 |
---|---|---|
yy |
复制当前行 | yank |
5yy |
复制 5 行 | 5次yank |
yw |
当光标在单词首字母处,复制当前单词 | yank word |
yaw |
当光标在单词内部,复制当前单词(单词后面空格也复制) | yank around word |
yiw |
当光标在单词内部,复制当前单词(单词后面空格不复制) | yank inside word |
p |
在光标后粘贴 | paste |
P |
在光标前粘贴 | Paste |
dd |
剪切当前行 | delete |
2dd |
剪切 2 行 | 2次delete |
dw /dW |
光标在单词首字母处,剪切当前单词 | delete word |
daw /daW |
剪切当前单词(后面有空格也剪切) | delete around word |
diw /diW |
剪切当前单词(后面有空格也剪切) | delete inside word |
D |
剪切, 从光标位置到行末 | Delete ⇥ |
x |
向后剪切掉一个字符,不用进入插入模式 | 向后x掉 |
X |
向前剪切掉一个字符,不用进入插入模式 | 向前X掉 |
J |
去掉行尾的换行符,即连接两行 | Join lines |
u |
撤销 | undo |
<ctrl-r> |
重做 | redo |
按键 | 效果 | 记忆方法 |
---|---|---|
<Ctrl + b> |
向后滚动一屏 | backwards |
<Ctrl + f> |
向前滚动一屏 | forwards |
<Ctrl + d> |
向后滚动半屏 | down |
<Ctrl + u> |
向前滚动半屏 | up |
按键 | 效果 | 记忆方法 |
---|---|---|
i |
从光标前开始插入字符 | insert |
I |
从行首开始插入字符 | Insert |
a |
从光标后开始插入字符 | append |
A |
从行尾开始插入字符 | Append |
o |
在当前行之下另起一行, 开始插入字符 | open a new line |
O |
在当前行之上另起一行, 开始插入字符 | Open a new line |
s |
删除当前字符,然后进入插入模式(替换) | substitute |
S |
删除当前行,然后进入插入模式(替换) | substitute |
r |
替换当前字符(其实是属于replace模式) | replace |
R |
替换连续的几个字符(属于replace模式) | Replace |
cw /cW |
删掉一个单词/带标点的单词,然后进入插入模式 | change |
C |
删除光标所在行的光标后面的内容 | Change |
<Esc> |
退出插入模式 | - |
按键 | 效果 | 记忆方法 |
---|---|---|
v |
选择字符 | visual |
V |
选择行 | Visual line |
<ctrl-v> |
选择块 | visual block |
gv |
重复上次的高亮区域 | - |
o |
结合可视模式用的o ,回到活动端点 |
- |
vw |
光标在单词首字母处,选择单词 | visual word |
vaw |
选择单词(包括单词后面的空格) | visual around world |
viw |
选择单词(不包括单词后面的空格) | visual inside world |
vit |
选择标签内的内容(html) | visual inside tags |
按键 | 效果 | 记忆方法 |
---|---|---|
:w |
保存、写入 | write |
:x /:wq |
保存并退出 | write quit |
:q! |
直接退出 | quit |
r filename |
读文件内容到当前文件中 | read filename |
w filename |
将当前文件内容另存到另一个文件 | write filename |
!command |
执行命令 | !command |
r!command |
读入命令的输出 | read !command |
:set number |
设置行符 | - |
:syntax on /:syntax off |
开启/关闭代码高亮 | - |
:s/target/replacement/
:替换当前行的第一个target
为replacement
:s/target/replacement/g
:替换当前行的所有的target
为replacement
:n,$s/target/replacement/
:替换第n到最后一行的第一个target
为replacement
:n,$s/target/replacement/g
:替换第n到最后一行的所有的target
为replacement
:%s/target/replacement
:替换所有行的第一个target
为replacement
:%s/target/replacement/g
:替换所有行的所有的target
为replacement
用#
或+
作为分隔符,/
作为匹配项中的内容:
:s#target/#/replacement#g
:替换所有行的第一个target/
为/replacement
:%s+/oradata/apras/+/user01/apras1+g
:替换所有行的/oradata/apras/
为/user01/apras1/
排序算法是《数据结构与算法》中最基本的算法之一。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:
n : 数据规模
K : 桶的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
Array.prototype.bubble_sort = function(){
var i,j,temp;
for(i=0;i<this.length-1;i++){
for(j=0;j<this.length-1-i;j++){
if(this[j]>this[j+1]){
temp = this[j];
this[j] = this[j+1];
this[j+1] = temp;
}
}
}
return this;
}
Array.prototype.selection_sort = function(){
var i,j,temp;
var min;
for(i=0;i<this.length-1;i++){
min=i;
for(j=i+1;j<this.length;j++)
if(this[min]>this[j])
min = j;
temp = this[min];
this[min] = this[i]
this[i] = temp;
}
return this;
}
Array.prototype.insertion_sort = function(){
var i,j,temp;
for(i=1;i<this.length;i++){
temp = this[i];
j = i-1;
for(;j>=0&&this[j]>temp;j--)
this[j+1] = this[j];
this[j+1] = temp;
}
return this;
}
Array.prototype.quick_sort = function(){
var len = this.length;
if(len<=1){
return this.slice(0)
}
var left = [],right = [],mid = [this[0]];
for(var i=1;i<len;i++){
if(this[i]<mid[0]){
left.push(this[i])
} else {
right.push(this[i])
}
}
return left.quick_sort().concat(mid.concat(right.quick_sort()));
}
Array.prototype.heap_sort = function(){
var arr = this.slice(0);
function swap(i,j){
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function max_heapify(start,end){
var dad = start;
var son = dad*2 +1;
if(son>=end)
return;
if(son+1<end&&arr[son]<arr[son+1])
son++;
if(arr[dad]<=arr[son]){
swap(dad, son);
max_heapify(son, end);
}
}
var len = arr.length;
for(var i=Math.floor(len/2)-1;i>=0;i--)
max_heapify(i,len);
for(var i=len-1;i>0;i--){
swap(0,i);
max_heapify(0,i);
}
return arr;
}
视觉上等价DOM结构上不等价、生成DOM不总是符合预期。
详细介绍:ContentEditable困境与破局
类型 | 优势 | 劣势 |
---|---|---|
L0 | 技术⻔槛低,短时间内快速研发 | 可定制的空间⾮常有限 |
L1 | 站在浏览器肩膀上,能够满⾜ 99% 业务场景 | ⽆法突破浏览器本身的排版效 |
L2 | 技术都掌控在⾃⼰⼿中,⽀持个性化排版 | 技术难度相当于⾃研浏览器、数据库 |
ProseMirror是一个用于实现富文本编辑器的开源框架。它提供了一个可定制、可扩展的编辑器,能够适应多种编辑场景,如在线文本编辑、协作编辑、内容管理系统等。作者 Marijn 是 codemirror
编辑器和 acorn
解释器的作者,前者已经在 Chrome
和 Firefox
自带的调试工具里使用了,后者则是 babel
的依赖,他还撰写了一本广受欢迎的 JavaScript 编程入门书籍《Eloquent JavaScript》
实现原理
ProseMirror依赖contentEditable,不过非常厉害的是ProseMirror将主流的前端的架构理念应用到了编辑器的开发中,比如彻底使用纯JSON数据描述富文本内容,引入不可变数据以及Virtual DOM的概念,还有插件机制、分层、Schemas(范式)等等。
特点:
核心模块
其他模块
使用
import {schema} from "prosemirror-schema-basic"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
let state = EditorState.create({schema})
let view = new EditorView(document.body, {state})
Prosemirror 定义了它自己的数据结构来表示 document 内容. 因为 document 是构建一个编辑器的核心元素, 因此理解 document 是如何工作的很有必要. document 是一个 node 类型, 它含有一个 fragment对象, fragment 对象又包含了 0 个或更多子 node.
结构
在 HTML 中, 一个 paragraph 及其中包含的标记, 表现形式就像一个树, 比如有以下 HTML 结构:
<p>This is <strong>strong text with <em>emphasis</em></strong></p>
Prosemirror 中, 内联元素被表示成一个扁平的模型, 他们的节点标记被作为 metadata 信息附加到相应 node 上
好处:可以使用字符串偏移量而不是树节点路径来表示在段落中的位置、spliting内容、改变内容style操作变的容易(树操作->数组下标)
Prosemirror document 就是一颗 block nodes 的树, 它的大多数 leaf nodes 是 textblock 类型, 该节点是包含 text 的 block nodes。Node 对象有一系列属性来表示它们在文档中的行为,如isBlock、isInline、inlineContent、isTextBlock、isLeaf ****等。
缺点:开发者重新学习它独有的描述DOM的范式。
数据结构
一个 document 的数据结构看起来像下面这样
不可变数据
ProseMirror document 和DOM树不同,它被设计成immutable,不可变的,nodes 仅仅是 values,它不跟当前数据结构绑定。这意味着每次你更新 document, 你就会得到一个新的 document。大多数情况下, 你需要使用 transform去更新 document 而不用直接修改 nodes。
优点:state 更新的时候编辑器始终可用,因为新的 state 就代表了新的 document, 新旧状态可以瞬间切换,这种机制使得协同编辑成为可能,新旧虚拟dom也可以通过diff算法实现高效的更新update DOM。
每个 Prosemirror document 都有一个与之相关的 schema. 这个 schema 描述了存在于 document 中的 nodes 类型, 和 nodes 们的嵌套关系. schema是骨架模版,nodes是不同类型的积木,通过组合搭积木的方式完成编辑器的组装。
const schema = new Schema({
nodes: {
doc: {content: "block+"},
paragraph: {group: "block", content: "text*", marks: "_"}, // 允许所有marks
heading: {group: "block", content: "text*", marks: ""}, // 0个多个 不允许使用marks
blockquote: {group: "block", content: "block+"}, // 1个多个
text: {inline: true}
},
marks: {
strong: {},
em: {}
}
})
// 传入state
let state = EditorState.create({schema})
import {schema} from "prosemirror-schema-basic"
import {EditorState} from "prosemirror-state"
let state = EditorState.create({schema})
console.log(state.doc.toString()) // An empty paragraph
console.log(state.selection.from) // 1, the start of the paragraph
Transactions
let tr = state.tr
console.log(tr.doc.content.size) // 25
tr.insertText("hello") // Replaces selection with 'hello'
let newState = state.apply(tr)
console.log(tr.doc.content.size) // 30
Plugins
1.当新建一个 plugin 的时候, 你需要传递 一个对象 来指定它的行为:
let myPlugin = new Plugin({
props: {
handleKeyDown(view, event) {
console.log("A key was pressed!")
return false // We did not handle this
}
}
})
let state = EditorState.create({schema, plugins: [myPlugin]})
2.当一个 plugin 需要它自己的 state,它可以定义自己的 state 属性:
let transactionCounter = new Plugin({
state: {
init() { return 0 },
apply(tr, value) { return value + 1 }
}
})
function getTransactionCount(state) {
return transactionCounter.getState(state)
}
3.插件使用时候,可以在transaction 上增加一些meta数据这样插件可以在执行transaction根据meta数据做不同的表现,增强插件的灵活性。
let transactionCounter = new Plugin({
state: {
init() { return 0 },
apply(tr, value) {
if (tr.getMeta(transactionCounter)) return value
else return value + 1
}
}
})
// set meta data
function markAsUncounted(tr) {
tr.setMeta(transactionCounter, true)
}
Transform系统是 Prosemirror 的核心工作方式. 它是 transactions的基础, 其使得编辑历史跟踪和协同编辑成为可能。
why?
Steps: 原子操作
一个编辑行为可能会产生一个或者多个 steps。例如: ReplaceStep、 AddMarkStep。
console.log(myDoc.toString()) // → p("hello")
// 删除了 position 在 3-5 的 setp
let step = new ReplaceStep(3, 5, Slice.empty)
let result = step.apply(myDoc)
console.log(result.doc.toString()) // → p("heo")
Transforms:
Transforms = (Steps+) ****一个编辑行为可能会产生一个或者多个 steps。支持链式调用
常见的方法 deleteing 和 replaceing, adding和 removeing marks 操作树数据结构的方法如 splitting, joining,lifting, 和 wrapping等
let tr = new Transform(myDoc)
tr.delete(5, 7) // Delete between position 5 and 7
tr.split(5) // Split the parent node at position 5
console.log(tr.doc.toString()) // The modified document
console.log(tr.steps.length) // → 2
Prosemirror 的 editor view 是一个用户界面的 component, 它展示 editor state给用户, 同时允许用户对其执行编辑操作。
Editable DOM
数据流
dispatch
派发一个 transaction。会调用 dispatchTransaction(如果设置了),否则默认应用该 transaction 到当前 state, 然后将其结果作为参数,传入 updateState方法,类似redux。
// The app's state
let appState = {
editor: EditorState.create({schema}),
score: 0
}
let view = new EditorView(document.body, {
state: appState.editor,
dispatchTransaction(transaction) {
update({type: "EDITOR_TRANSACTION", transaction})
}
})
// A crude app state update function, which takes an update object,
// updates the `appState`, and then refreshes the UI.
function update(event) {
if (event.type == "EDITOR_TRANSACTION")
appState.editor = appState.editor.apply(event.transaction)
else if (event.type == "SCORE_POINT")
appState.score++
draw()
}
// An even cruder drawing function
function draw() {
document.querySelector("#score").textContent = appState.score
view.updateState(appState.editor)
}
Efficient updating
Prosemirror 内部有一些高效的更新策略。比如diff新旧虚拟dom 只更新变化部分、比如更新输入的文本,这些文本通过dom的方式被修改,Prosemirror 监听 DOM change 事件, 然后由此触发 transaction 将 DOM 的输入变化同步过来, 不需要再修改 DOM等。
Props
属性定义了组件的行为,这个概念来自React
let view = new EditorView({
state: myState,
editable() { return false }, // Enables read-only behavior
handleDoubleClick() { console.log("Double click!") }
})
Decorations
Decorations 给了你绘制你的 document view 方面的一些能力
let purplePlugin = new Plugin({
props: {
decorations(state) {
return DecorationSet.create(state.doc, [
Decoration.inline(0, state.doc.content.size, {style: "color: purple"})
])
}
}
})
Node views
一系列小型且独立的 node 的 UI component
let view = new EditorView({
state,
nodeViews: {
image(node) { return new ImageView(node) }
}
})
class ImageView {
constructor(node) {
// The editor will use this as the node's DOM representation
this.dom = document.createElement("img")
this.dom.src = node.attrs.src
this.dom.addEventListener("click", e => {
console.log("You clicked me!")
e.preventDefault()
})
}
stopEvent() { return true }
}
prosemirror-commands模块提供了大量的编辑 commands,可以让用户通过按一些联合按键来执行操作或者菜单交互行为,通过工具函数函数 chainCommands可以实现命令的组合,除此外也支持用户自定义command函数。
/// Delete the selection, if there is one.
export const deleteSelection: Command = (state, dispatch) => {
if (state.selection.empty) return false
if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView())
return true
}
基于 ProseMirror的无头编辑器. Tiptap提供了合理的默认值、通过抽象Extension扩展的提供统一的写法,增加了更多commonds便于实际使用,整体提高了编辑器的可用性和扩展性,可以作为开箱即用的工程化方案。
基于[ProseMirror](https://github.com/ProseMirror/prosemirror)的用于构建*跨平台*文本编辑器的 React工具包
BlockNote 是一个Notion风格的基于ProseMirror和Tiptap构建的可扩展块的文本编辑器
https://prosemirror.net/docs/guide/
https://tiptap.dev/introduction
开源富文本编辑器技术的演进(2020 1024)
https://www.wenxi.tech/principles-of-modern-editor-technology
在JavaScript中调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义形参,每个函数接收两个附加的参数:this
和arguments
。参数this
在面向对象中非常重要,它取决于调用的模式。在JavaScript**有四种**调用模式:**方法调用模式、函数调用模式、构造器调用模式、和apply()
,call()
方法调用模式。这些模式在如何初始化关键参数this存在差异。本文首先要提到的是this,抛开this单独去说这些方法是没有意义的。然后是如何妙用call,apply,bind这些方法去改变this
的指向。
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
//直接调用
function f1(){
return this;
}
f1() === window; // true
//this的值不是由函数调用设定。因为代码不是在严格模式下执行,this 的值总是一个对象且默认为全局对象
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
//在严格模式下,this 是在进入运行环境时设置的。若没有定义,this的值将维持undefined状态。也可能设置成任意值。
var o = {
prop: 38,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 38
注意,在何处或者如何定义调用函数完全不会影响到this的行为。在上一个例子中,我们在定义o的时候为其成员f定义了一个匿名函数。但是,我们也可以首先定义函数然后再将其附属到o.f。这样做this的行为也一致:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
这说明this的值只与函数 f 作为 o 的成员被调用有关系。
类似的,this 的绑定只受最靠近的成员引用的影响。在下面的这个例子中,我们把一个方法g当作对象o.b的函数调用。在这次执行期间,函数中的this将指向o.b。事实上,这与对象本身的成员没有多大关系,最靠近的引用才是最重要的。
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
在这个例子中,对象p没有属于它自己的f属性,它的f属性继承自它的原型。但是这对于最终在o中找到f属性的查找过程来说没有关系;查找过程首先从p.f的引用开始,所以函数中的this指向p。也就是说,因为f是作为p的方法调用的,所以它的this指向了p。这是JavaScript的原型继承中的一个有趣的特性。
5. getter 与 setter 中的 this
再次,相同的概念也适用时的函数作为一个 getter 或者 一个setter调用。作为getter或setter函数都会绑定 this 到从设置属性或得到属性的那个对象。
function modulus(){
return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
re: 1,
im: -1,
get phase(){
return Math.atan2(this.im, this.re);
}
};
Object.defineProperty(o, 'modulus', {
get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
// 被调用时,将关联的元素变成蓝色
function bluify(e){
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象是为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
fun.apply(thisArg[, argsArray])
方法在指定 this 值和参数(参数以数组或类数组对象的形式存在)的情况下调用某个函数。
在调用一个存在的函数时,你可以为其指定一个 this 对象。 this指当前对象,也就是正在调用这个函数的对象。使用apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量.你也可以使用 arguments
对象作为 argsArray
参数。 arguments
是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。
Function.prototype.construct = function (aArgs) {
var oNew = Object.create(this.prototype);
this.apply(oNew, aArgs);
return oNew;
};
//另一种可选的方法是使用闭包
Function.prototype.construct = function(aArgs) {
var fConstructor = this, fNewConstr = function() {
fConstructor.apply(this, aArgs);
};
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
};
function minOfArray(arr) {
var min = Infinity;
var QUANTUM = 32768;
for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
min = Math.min(submin, min);
}
return min;
}
var min = minOfArray([5, 6, 2, 3, 7]);
var originalfoo = someobject.foo;
someobject.foo = function() {
//在调用函数前干些什么
console.log(arguments);
//像正常调用这个函数一样来进行调用:
originalfoo.apply(this,arguments);
//在这里做一些调用之后的事情。
}
fun.call(thisArg[, arg1[, arg2[, ...]]])
使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.该方法的作用和 apply()
方法类似,只有一个区别,就是call()
方法接受的是若干个参数的列表,而apply()
方法接受的是一个包含多个参数的数组。通过 call 方法,你可以在一个对象上借用另一个对象上的方法,比如Object.prototype.toString.call([])
,就是一个Array对象借用了Object对象上的方法。
function Product(name, price) {
this.name = name;
this.price = price;
if (price < 0) {
throw RangeError('Cannot create product ' +
this.name + ' with a negative price');
}
return this;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
Food.prototype = Object.create(Product.prototype);
Food.prototype.constructor = Food; // Reset the constructor from Product to Food
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
Toy.prototype = Object.create(Product.prototype);
Toy.prototype.constructor = Toy; // Reset the constructor from Product to Toy
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
var animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
];
for (var i = 0; i < animals.length; i++) {
(function (i) {
this.print = function () {
console.log('#' + i + ' ' + this.species + ': ' + this.name);
}
this.print();
}).call(animals[i], i);
}
function greet() {
var reply = [this.person, 'Is An Awesome', this.role].join(' ');
console.log(reply);
}
var i = {
person: 'Douglas Crockford', role: 'Javascript Developer'
};
greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer
当一个函数的函数体中使用了this关键字时,通过所有函数都从Function对象的原型中继承的call()方法和apply()方法调用时,它的值可以绑定到一个指定的对象上。
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
// The first parameter is the object to use as 'this', subsequent parameters are passed as
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
使用 call 和 apply 函数的时候要注意,如果传递的 this 值不是一个对象,JavaScript 将会尝试使用内部 ToObject 操作将其转换为对象。因此,如果传递的值是一个原始值比如 7 或 'foo' ,那么就会使用相关构造函数将它转换为对象,所以原始值 7 通过new Number(7)被转换为对象,而字符串'foo'使用 new String('foo') 转化为对象,例如
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
fun.bind(thisArg[, arg1[, arg2[, ...]]])
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
his.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX(); // 9, because in this case, "this" refers to the global object
// Create a new function with 'this' bound to module
//New programmers (like myself) might confuse the global var getX with module's property getX
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后, 调用'declare'方法
var slice = Array.prototype.slice;
// ...
slice.apply(arguments);
用 bind() 可以使这个过程变得简单。在下面这段代码里面,slice 是 Function.prototype 的 call() 方法的绑定函数,并且将 Array.prototype 的 slice() 方法作为 this 的值。这意味着我们压根儿用不着上面那个 apply() 调用了。
// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
// ...
slice(arguments);
ECMAScript 5 引入了 Function.prototype.bind
。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
以上内容大部分来自MDN读者可以自行去研究;
just test!!!
学过JavaScript这门语言的同学想必都听说过闭包一词, 闭包在日常开发中用的也是非常广泛的。可是当面试问你闭包是什么的时候,总感觉说不到点上。究竟什么是闭包呢?它是怎么起作用的?如何使用闭包去简化开发?如何用闭包提高性能以及如何用闭包解决作用域的问题?本文就为大家解开闭包的真面纱。
首先要知道什么是闭包就得知道闭包是怎么产生的。在javascript中只有函数存在作用域,并且函数可以被嵌套使用,当我们使用函数套用的时候就产生了一个有趣的现象,我们在子函数中可以任意访问外部函数中定义的变量和方法,但是外面的函数却得不到里面函数中定义的变量。也就是子作用域被封闭起来。那么问题来了,如何拿到子作用域里的变量呢?因此闭包出现了。也就是说闭包的唯一目的就是获取子作用域。
知道了闭包产生的目的后,我们来看看MDN对闭包的解释。
闭包是指能够访问自由变量的函数。换句话说,定义在闭包中的函数可以“记忆”它被创建时候的环境。
MDN解释的这么晦涩难懂,摆明不想让大家学会闭包。
来看看jQuery作者对闭包的定义吧:
a closure is the scope created when a function is declared that allows the
function to access and manipulate variables that are external to that function.
Put another way, closures allow a function to access all the variables, as well as other functions, that are in scope when the function itself is declared.
翻译成汉语其实就是闭包可以获取,操作内部作用域中定义的变量。
看完这段英语相信你已经对闭包有一定的感性理解。那么我们来从语法的角度分析一下闭包的组成。
它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。一说环境可能有点高深,我用数学表达来表述就是:闭包=函数(可匿名)+环境(作用域+局部变量+入参) 这下是不是很清晰了。
JavaScript 并不提供原生的支持,但是可以使用闭包模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。这种方式也称为 模块模式
module import variant
(function(window){
//私有属性
var privateThing;
function privateMethod(){
//私有方法
}
window.api = {
//暴露公共接口
}
})(window)
module pattern
var api = (function(){
// Private and in-place!
var local = 0;
//私有作用域
function counter () {
return ++local;
}
//暴露给api
return {
counter: counter
};
})();
api.counter();
// <- 1
api.counter();
// <- 2
2.回调和计时器 (Callback and Timer)
在处理回调和计时器的时候我们可以使用闭包,这两个函数都需要异步被调用而且会很频繁的获取外部的数据.
异步回调(cb)最常见的是Ajax请求,例子就不举了用过jquery的同学都知道.
Timer
//这是一个60秒倒计时的计时器
var step = 0;
var timer = setInterval(function(){
if(step<60){
step++;
}else{
clearInterval(timer);
}
},1000);
3.绑定函数作用域(Binding function contexts)
function bind(context,name){
return function(){
return context[name].apply(context,arguments);
};
}
4.参数归并技术以及函数柯里化 (Argument-merging technique and currying)
柯里化是指我们可以部分调用函数(返回我们预定义参数的函数),这个函数可以被以后调用.简单来说就是我们可以首先填充函数的一部分参数(并返回新的函数)这种技术通常被叫做函数柯里化。因为参数开始被填充了一部分到函数,后面一边通过返回新的函数里引用剩余的参数.所以形象的叫做参数归并技术。其本质是一样的。
举个例子:
Function.prototype.curry = function() {
var fn = this,
args = Array.prototype.slice.call(arguments);
return function() {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments)));
};
};
Function.prototype.partial = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
var arg = 0;
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
return fn.apply(this, args);
};
};
5.记忆和包裹函数(Memoization and Function wrapping)
Function.prototype.memoized = function(key){
this._values = this._values || {};
return this._values[key] !== undefined ?
this._values[key] :
this._values[key] = this.apply(this, arguments);
};
function isPrime(num) {
var prime = num != 1;
for (var i = 2; i < num; i++) {
if (num % i == 0) {
prime = false;
break;
on behavior 107
}
}
return prime;
}
//记忆
function memoizer(memo,formula){
var recur = function(n){
var result = memo[n];
if (typeof result !== 'number'){
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
}
//阶乘
var factorial = memoizer([1,1],function(recur,n){
return n*recur(n-1);
})
//斐波那契数列
var fibonacci = memozier([0,1],function(recur,n){
return recur(n-1)+recur(n-2);
})
wrap function
function wrap(object, method, wrapper) {
var fn = object[method];
return object[method] = function() {
return wrapper.apply(this, [fn.bind(this)].concat(
Array.prototype.slice.call(arguments)));
};
}
6.立即执行函数(IIFE)
几种立即执行的写法
(function() {
// child scope
})();
!function () {
// child scope
}();
+function () {
// child scope
}();
-function () {
// child scope
}();
~function () {
// child scope
}();
void function () {
// child scope
}();
1^function () {
// child scope
}();
1&function () {
// child scope
}();
查看指定端口号语法格式:lsof -i:端口号
如果命令找不到 yum install lsof -y
[root@localhost ~]# lsof -i:7075
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 17394 root 4u IPv6 75775 0t0 TCP *:7075 (LISTEN)
查看所有端口占用: lsof -i
[root@vultr ~]# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
chronyd 421 chrony 5u IPv4 15794 0t0 UDP localhost:323
chronyd 421 chrony 6u IPv6 15795 0t0 UDP localhost:323
dhclient 759 root 6u IPv4 16886 0t0 UDP *:bootpc
sshd 822 root 3u IPv4 18590 0t0 TCP *:ssh (LISTEN)
sshd 822 root 4u IPv6 18599 0t0 TCP *:ssh (LISTEN)
python 984 root 4u IPv6 19154 0t0 TCP *:cbt (LISTEN)
python 984 root 6u IPv6 19155 0t0 UDP *:cbt
python 984 root 9u IPv4 19174 0t0 UDP *:37738
用于显示 tcp,udp 的端口和进程等相关情况:netstat -tunlp
netstat 查看端口占用语法格式:netstat -tunlp | grep 端口号
[root@localhost ~]# netstat -tunlp | grep 7075
tcp6 0 0 :::7075 :::* LISTEN 17394/docker-proxy
[root@localhost ~]#
[root@localhost ~]# netstat -tunlp | grep 3306
tcp6 0 0 :::3306 :::* LISTEN 19356/mysqld
[root@localhost ~]#
[root@localhost ~]# netstat -tunlp | grep 1521
tcp6 0 0 :::1521 :::* LISTEN 17470/docker-proxy
[root@localhost ~]#
[root@localhost ~]# netstat -tunlp | grep 1525
[root@localhost ~]#
[root@localhost ~]# netstat -tunlp | grep 1522
tcp6 0 0 :::1522 :::* LISTEN 17436/docker-proxy
[root@localhost ~]#
显示本用户的进程:ps
[root@localhost ~]# ps
PID TTY TIME CMD
30059 pts/0 00:00:00 bash
31149 pts/0 00:00:00 ps
显示所有用户进程:ps aux
显示指定进程和其状态:ps -aux | grep 服务
[root@localhost ~]# ps -aux | grep httpd
root 31122 0.0 0.0 112680 696 pts/0 S+ 14:01 0:00 grep --color=auto httpd
[root@localhost ~]#
[root@localhost ~]# ps -aux | grep vsftpd
root 31124 0.0 0.0 112680 696 pts/0 S+ 14:01 0:00 grep --color=auto vsftpd
终止进程:kill -9 [PID]
在视频网站经常见到flash播放器需要加载flash插件才能播放,那么如何检测浏览器是否已有插件或者以经下载。
其实就是利用了浏览器插件检测的办法。IE检查插件是否安装,通过创建ActiveXObject来实现,而FF,Chrome等浏览器,是通过创建navigator.plugins来实现。
很简单直接看代码
function flashCheck() {
let swf,
swf_ver,
hasFlash = false,
ver;
try {
if (document.all) {
swf = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
if (swf) {
hasFlash = true;
swf_ver = swf.GetVariable('$version');
}
} else {
if (navigator.plugins && navigator.plugins.length > 0) {
swf = navigator.plugins['Shockwave Flash'];
if (swf) {
hasFlash = true;
swf_ver = swf.description;
}
}
}
if (typeof (swf_ver) !== 'string') {
swf_ver = '';
}
ver = (swf_ver || '0 r0').match(/\d+/g);
}
catch (e) {
}
return {
hasFlash, // has flash
ver // flash version
}
}
如果浏览器没有安装flash可以通过下面方式开启, 即一个a标签_blank打开即可。
💖💖💖💖💖💖💖💖💖💖下载Flash💖💖💖💖💖💖💖💖💖💖
have a fun day!
Web浏览器正在朝着更严格的自动播放策略发展,以便改善用户体验,最大限度地降低安装广告拦截器的积极性并减少昂贵和/或受限网络上的数据消耗。这些更改旨在为用户提供更大的播放控制权,并使开发商获得合法用例。
Chrome的自动播放政策很简单:
MEI衡量个人在网站上消费媒体的倾向。Chrome 目前的方法是访问每个来源的重要媒体播放事件的比率:
因此,Chrome会计算媒体参与度分数,该分数在定期播放媒体的网站上最高。足够高时,媒体播放只允许在桌面上自动播放。MEI是谷歌自动播放策略的一部分。它是一个算法,参考了媒体内容的持续时间、浏览器标签页是否活动、活动标签页视频的大小这一系列元素。不过也正因此,开发者难以在所有的网页上都测试这一算法的效果。
用户的MEI位于chrome://media-engagement/内部页面
作为开发者,您可能需要在本地更改Chrome浏览器自动播放政策行为,以根据用户的参与情况测试您的网站。
一个功能政策使开发人员可以选择性地启用和禁用的各种浏览器的功能和API。一旦来源获得了自动播放权限,它就可以将该权限委托给具有自动播放功能的跨源iframe 。默认情况下,同源iframe可以使用自动播放。
<! - 允许自动播放。- >
<iframe src = "https://cross-origin.com/myvideo.html" allow = "autoplay" />
<! - 允许自动播放和全屏播放。- >
<iframe src = "https://cross-origin.com/myvideo.html" allow = "autoplay; fullscreen" />
当禁用自动播放的功能策略时,play()不带用户手势的调用将拒绝带有NotAllowedErrorDOMException 的promise。自动播放属性也将被忽略。
示例场景:
示例1:每次用户在他们的笔记本电脑上访问www.iqiyi.com时,他们都会观看电视节目或电影。由于其媒体参与度较高,因此可以自动播放。
示例2:www.iqiyi.com同时具有文字和视频内容。大多数用户偶尔会去该网站获取文字内容并观看视频。用户的媒体参与度较低,因此如果用户直接从社交媒体页面或搜索导航,则不允许自动播放。
示例3:news.iqiyi.com同时具有文字和视频内容。大多数人通过主页进入网站,然后点击新闻报道。由于用户与域名互动,新闻文章页面上的自动播放将被允许。但是,应该注意确保用户不会对自动播放内容感到意外。
示例4: 在爱奇艺泡泡页面将iframe与电影预告片一起嵌入其评论中。用户与域进行交互以访问特定的网站,因此允许自动播放。但是,泡泡需要将该特权显式委托给iframe以便内容自动播放。
Chrome企业策略可以改变这种新的自动播放行为,以用于例如信息亭或无人值守系统。查看 配置策略和设置帮助页面,了解如何设置这些新的与自动播放相关的企业策略:
var promise = document.querySelector('video').play();
if (promise !== undefined) {
promise.then(_ => {
// Autoplay started!
}).catch(error => {
// Autoplay was prevented.
// Show a "Play" button so that user can start playback.
});
}
<video id="video" muted autoplay>
<button id="unmuteButton"></button>
<script>
unmuteButton.addEventListener('click', function() {
video.muted = false;
});
</script>
站点 | 处理方式 |
---|---|
微博 | 静音开播 |
优酷 | 开播暂停 |
腾讯 | 部分静音开播部分暂停 |
爱奇艺 | 静音开播 部分暂停 |
B站 | 暂未处理 |
以上情况截止本文发表前部分页面统计不代表全部。
原生播放音频除了使用audio标签之外,还有另外一个API叫AudioContext,AudioContext接口表示由音频模块连接而成的音频处理图,每个模块对应一个AudioNode。AudioContext可以控制它所包含的节点的创建,以及音频处理、解码操作的执行。做任何事情之前都要先创建AudioContext对象,因为一切都发生在这个环境之中。
function request (url) {
return new Promise (resolve => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
// set response Type arraybuffer
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.response);
}
};
xhr.send();
});
}
// Safari是使用webkit前缀
let context = new (window.AudioContext || window.webkitAudioContext)();
function play (context, decodeBuffer) {
let source = context.createBufferSource();
source.buffer = decodeBuffer;
source.connect(context.destination);
// 从0s开始播放
source.start(0);
}
// 请求音频数据
let audioMedia = await request(url);
// 进行decode和play
context.decodeAudioData(audioMedia, decode => play(context, decode));
关于音频播放的可以参阅这篇文章讲的比较详细,这里不再讨论。
// Existing code unchanged.
window.onload = function() {
var context = new AudioContext();
// Setup all nodes
...
}
// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
context.resume().then(() => {
console.log('Playback resumed successfully');
});
});
document.querySelector('button').addEventListener('click', function() {
var context = new AudioContext();
// Setup all nodes
...
});
url =>base64
let urlToBase64 = url =>
new Promise((resolve, reject) => {
let image = new Image();
image.onload = function() {
let canvas = document.createElement("canvas");
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
canvas.getContext("2d").drawImage(image, 0, 0);
let ret = canvas.toDataURL("image/png");
resolve(ret);
};
image.setAttribute("crossOrigin", "Anonymous");
image.src = url;
image.onerror = e => {
reject(e);
};
});
base64 => blob
let base2Blob = ({ b64data = "", contentType = "", sliceSize = 512 } = {},filename) =>
new Promise((resolve, reject) => {
let byteChars = atob(b64data);
let byteArrays = [];
for (let offset = 0; offset < byteChars.length; offset += sliceSize) {
let slice = byteChars.slice(offset, offset + sliceSize);
let byteNumbers = [];
for (let i = 0; i < slice.length; i++) {
byteNumbers.push(slice.charCodeAt(i));
}
byteArrays.push(new Uint8Array(byteNumbers));
}
let ret = new Blob(byteArrays, { type: contentType });
ret = Object.assign(ret, {
preview : URL.createObjectURL(ret);
name: `${filename}.png`
})
});
blob =>base64
let blob2Base64 = (blob) => new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
resolve(e.target.result);
}
fileReader.readAsDataURL(blob);
fileReader.onerror = (e) => {
reject(e);
}
});
JavaScript是无法获得或存储在客户端的IP。但是由于JavaScript能够发送HTTP请求,而服务器端语言能够获取用户的公网IP,所以你可以利用这个获取IP。 换句话说,如果你想得到一个用户就取决于请求任何服务器检索公网IP。 随着WebRTC技术的发展,利用rtcpeerconnection可以检索用户私有IP。
RTCPeerConnection技术详细可见MDN
/**
* Get the user IP throught the webkitRTCPeerConnection
* @param onNewIP {Function} listener function to expose the IP locally
* @return undefined
*/
function getUserIP(onNewIP) { // onNewIp - your listener function for new IPs
//compatibility for firefox and chrome
var myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new myPeerConnection({
iceServers: []
}),
noop = function() {},
localIPs = {},
ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g,
key;
function iterateIP(ip) {
if (!localIPs[ip]) onNewIP(ip);
localIPs[ip] = true;
}
//create a bogus data channel
pc.createDataChannel("");
// create offer and set local description
pc.createOffer().then(function(sdp) {
sdp.sdp.split('\n').forEach(function(line) {
if (line.indexOf('candidate') < 0) return;
line.match(ipRegex).forEach(iterateIP);
});
pc.setLocalDescription(sdp, noop, noop);
}).catch(function(reason) {
// An error occurred, so handle the failure to connect
});
//listen for candidate events
pc.onicecandidate = function(ice) {
if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return;
ice.candidate.candidate.match(ipRegex).forEach(iterateIP);
};
}
// Usage
getUserIP(function(ip){
alert("Got IP! :" + ip);
});
$.getJSON('http://ipinfo.io', function(data){
console.log(data);
});
API URI | Response Type | Sample Output (IPv4) | Sample Output (IPv6) |
---|---|---|---|
https://api.ipify.org | text | 11.111.111.111 | ? |
https://api.ipify.org?format=json | json | {"ip":"11.111.111.111"} | ? |
https://api.ipify.org?format=jsonp | jsonp | callback({"ip":"11.111.111.111"}) | ? |
https://api.ipify.org?format=jsonp&callback=getip | jsonp | getip({"ip":"11.111.111.111"}); | ? |
可以使用jsonp形式在页面上。
<script type="application/javascript">
function getIP(json) {
document.write("My public IP address is: ", json.ip);
}
</script>
<script type="application/javascript" src="https://api.ipify.org?format=jsonp&callback=getIP"></script>
Have fun 🥇
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
分类 | 分类描述 |
---|---|
1xx | 信息性,服务器收到请求,需要请求者继续执行操作 |
2xx | 成功,操作被成功接收并处理 |
3xx | 重定向,需要进一步的操作以完成请求 |
4xx | 客户端错误,请求包含语法错误或无法完成请求 |
5xx | 服务器错误,服务器在处理请求的过程中发生了错误 |
状态码 | 原因短语 | 含义 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但服务器还未对其执行任何动作。 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但没有实体的主体部分。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 成功执行了一个部分或者Range范围请求。 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 当前未使用 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
主干开发是一套代码分支管理策略,开发人员之间通过约定向被指定为主干的分支提交代码,以此抵抗因为长期存在的多分支导致的开发压力。 此举可避免分支合并的困扰,保证随时拥有可发布的版本。
在介绍主干开发前先来对比一下目前的分支管理策略。
Git分支配置的规则,也是实现该规则的工具 最完整的体系。
Git Flow 对于大部分开发人员和团队来说,稍微有些复杂,而且没有 GUI 图形页面,只能命令行操作,所以为了更好的解决这些问题,GitHub Flow 应运而生了。
结合了Git Flow和GitHub Flow的优点,折中的版本。
清晰可控,由于修正是在master层面,所以确保所有的提交都是测试环境中通过测试的。
需要进行预发布/周期性版本发布的项目。
全名Trunk-based development、略称TBD.基于主干开发,所有开发人员都只能在一个开发分支中开发。
主干分支是所有开发人员公用的,一个开发人员引入的 bug 可能对其他很多人造成影响。
协作能力强的小规模团队。
https://cloud.google.com/solutions/devops/devops-tech-trunk-based-development?hl=zh-cn
https://cn.trunkbaseddevelopment.com/
javascript对函数的实现是最好的,函数可以说是javascript的灵魂,它几乎可以说是无所不能的,函数包含一组语句,他们是JavaScript的基础模块单元,用与代码复用、信息隐藏和组合调用。函数用与指定对象行为。
所谓编程就是将一组需求分解成函数与数据结构的技能
程序设计=数据结构+算法
本文只是简单列举出函数的重要特性,由于每种特性不是三两两语就能说清楚,干脆不在一篇文章中介绍。后续会一一介绍。文章列举属性来自JavaScript语言精粹。
Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。
Markdown具有一系列衍生版本,用于扩展Markdown的功能(如表格、脚注、内嵌HTML等等),这些功能原初的Markdown尚不具备,它们能让Markdown转换成更多的格式,例如LaTeX,Docbook。Markdown增强版中比较有名的有Markdown Extra、MultiMarkdown、 Maruku等。这些衍生版本要么基于工具,如Pandoc;要么基于网站,如GitHub和Wikipedia,在语法上基本兼容,但在一些语法和渲染效果上有改动。
Markdown的语法简洁明了、学习容易,而且功能比纯文本更强,因此有很多人用它写博客。世界上最流行的博客平台WordPress和大型CMS如Joomla、Drupal都能很好的支持Markdown。完全采用Markdown编辑器的博客平台有Ghost和Typecho。
用于编写说明文档,并且以“README.MD”的文件名保存在软件的目录下面。
详细可见 github
官方文档 mastering-markdown
#定义Nginx运行的用户和用户组
user www www;
#nginx进程数,建议设置为等于CPU总核心数。
worker_processes 8;
#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
error_log /var/log/nginx/error.log info;
#进程文件
pid /var/run/nginx.pid;
# 一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)
# 与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。
worker_rlimit_nofile 65535;
#工作模式与连接数上限
events {
#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ];epoll模型是Linux 2.6以上
#版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll;
#单个进程最大连接数(最大连接数=连接数*进程数)
worker_connections 65535;
}
http {
#设定http服务器
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型
#charset utf-8; #默认编码
server_names_hash_bucket_size 128; #服务器名字的hash表大小
client_header_buffer_size 32k; #上传文件大小限制
large_client_header_buffers 4 64k; #设定请求缓
client_max_body_size 8m; #设定请求缓
sendfile on; # 开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,
# 对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘
# 与网络 I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on; #防止网络阻塞
tcp_nodelay on; #防止网络阻塞
keepalive_timeout 120; #长连接超时时间,单位是秒
#FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
#gzip模块设置
gzip on; #开启gzip压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 2; #压缩等级
gzip_types text/plain application/x-javascript text/css application/xml;
# 压缩类型,默认就已经包含text/html,所以下面就不用再写了,但是会有一个warn。
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用
Nginx的负载均衡模块目前支持4种调度算法。weight 轮询,ip_hash,fair,url_hash。
upstream www.xx.com {
# upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越
# 高被分配到的几率越大。
server 192.168.80.121:80 weight=3;
server 192.168.80.122:80 weight=2;
server 192.168.80.123:80 weight=3;
}
#虚拟主机的配置
server {
# 监听端口
listen 80;
#域名可以有多个,用空格隔开
server_name www.xx.com;
ssi on; # Server Side Include,通常称为服务器端嵌入
index index.html index.htm index.php;
root /data/www/www.xx.com;
# 图片缓存时间设置
location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 10d;
}
# JS和CSS缓存时间设置
location ~ .*.(js|css)?$ {
expires 1h;
}
# 日志格式设定
log_format access '$remote_addr – $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
# 定义本虚拟主机的访问日志
access_log /var/log/nginx/ospring.pw.log access;
#对 "/" 启用反向代理
location / {
proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以下是一些反向代理的配置,可选。
proxy_set_header Host $host;
client_max_body_size 10m; #允许客户端请求的最大单文件字节数
client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数,
proxy_connect_timeout 90; #nginx跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 90; #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时)
proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k; #proxy_buffers缓冲区,网页平均在32k以下的设置
proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k;
#设定缓存文件夹大小,大于这个值,将从upstream服务器传
}
#设定查看Nginx状态的地址
location /NginxStatus {
stub_status on;
access_log on;
auth_basic "NginxStatus";
# htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
auth_basic_user_file conf/htpasswd;
}
# 本地动静分离反向代理配置
# 所有 jsp 的页面均交由tomcat或resin处理
location ~ .(jsp|jspx|do)?$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
# 所有静态文件由nginx直接读取不经过tomcat或resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
{
root /data/www/www.xx.com/public;
expires 15d;
}
location ~ ^/(upload|html)/ {
root /data/www/www.xx.com/public/html;
expires 30d;
}
include vhosts/*.conf; 分割配置文件,方便管理
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate cert.pem;
ssl_certificate_key cert.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
$ brew install python3
$ cd /usr/local
$ sudo chown -R $(whoami) $(brew --prefix)/*
try again 😡
$ brew install python3
$ brew unlink python && brew link python
$ brew link --overwrite python3
$ sudo brew link --overwrite python3
$ sudo mkdir /usr/local/Frameworks
$ sudo chown $(whoami):admin /usr/local/Frameworks
$ brew link python
成功!😄
随着 4G 的普遍以及 WiFi 的广泛使用,手机上的网速已经足够稳定和高速,以视频为主的 HTML5 也越来越普遍了,相比帧动画,视频的表现更加丰富,这里介绍一些实践经验。
<video
id="video"
src="video.mp4"
controls = "true"
poster="images.jpg" // 视频封面
preload="auto"
webkit-playsinline="true" /* 这个属性是ios 10中设置可以让视频在小窗内播放,也就是不是全屏播放*/
playsinline="true" // IOS微信浏览器支持小窗内播放
x-webkit-airplay="allow"
x5-video-player-type="h5" // 启用H5播放器,是wechat安卓版特性
x5-video-player-fullscreen="true" // 全屏设置,设置为 true 是防止横屏
x5-video-orientation="portraint" // 播放器的方向, landscape横屏,portraint竖屏,默认值为竖屏
style="object-fit:fill">
</video>
<video id="video" src="xx.mp4" playsinline webkit-playsinline></video>
android始终不能自动播放,不多说。值得一提的是经测现在ios10后版本的safari和微信都不让视频自动播放了(顺带音频也不能自动播放了),但微信提供了一个事件WeixinJSBridgeReady,在微信嵌入webview全局的这个事件触发后,视频仍可以自动播放,这个应该是现在在ios端微信的视频自动播放的比较靠谱的方式,其他如手q或者其他浏览器,建议就引导用户出发触屏的行为操作出发比较好。
document.addEventListener("WeixinJSBridgeReady", function (){
video.play();
video.pause();
}, false)
ios10以后对于video出了新策 感兴趣可以了解一波 https://webkit.org/blog/6784/new-video-policies-for-ios/
对于video或者audio等媒体元素,有一些方法,常用的有play(),pause();也有一些事件,如loadstart
,canplay
,canplaythrough
,ended
,timeupdate
....等等。
在移动端有一些坑需要注意,不要轻易使用媒体元素的除ended
,timeupdate
以外event事件,在不同的机子上可能有不同的情况产生,例如:ios下监听canplay
和canplaythrough
(是否已缓冲了足够的数据可以流畅播放),当加载时是不会触发的,即使preload="auto"
也没用,但在pc的chrome调试器下和android下,是会在加载阶段就触发。ios需要播放后才会触发。总之就是现在的视频标准还不尽完善,有很多坑要注意,要使用前最好自己亲测一遍。就是当第一次播放视频的时候ios端,如果网络慢,视频从开始播到能展现画面会有短暂的黑屏(处理视频源数据的时间),为了避免这个黑屏,可以在视频上加个div浮层(可以一个假的视频第一帧),然后用timeupdate
方法监听,视屏播放及有画面的时候再移除浮层。
video.addEventListener('timeupdate',function (){
//当视频的currentTime大于0.1时表示黑屏时间已过,已有视频画面,可以移除浮层(.pagestart的div元素)
if ( !video.isPlayed && this.currentTime>0.1 ){
$('.pagestart').fadeOut(500);
video.isPlayed = !0;
}
})
据说腾讯的android团队的x5内核团队放开了视频播放的限制,视频不一定调用它们那个备受诟病的视频播放器了,x5-video-player-type="h5"
属性这个属性好像就有点那个意思,虽然体验还是有点...(导航栏也会清理)但至少播放器控件没有了,上层可以浮div或者其他元素了,这个还是值得一提。还有一点值得说的是,带播放器控件的隐藏.
<div class="videobox" ontouchmove="return false;">
<video id="mainvideo" src="test.mp4" x5-video-player-type="h5" playsinline webkit-playsinline></video>
</div>
比如这个videobox在android下隐藏,只用display:none貌似还是不行的,但加个z-index:-1,设置成-1就可以达到隐藏播放器控件的目的了。
html5--移动端视频video的android兼容,去除播放控件、全屏等
MDN-Video
视频H5のVideo标签在微信里的坑和技巧
移动端HTML5
微信端视频播放问题
使用window.URL.createObjectURL和window.URL.revokeObjectURL method和blob对象实现文件下载
/**
* 创建并下载文件
* @param {String} fileName 文件名
* @param {String} content 文件内容
*/
function saveAs(content, filename) {
var link = document.createElement('a');
var blob = new Blob([content]);
link.download = filename;
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(blob);
}
var URL = window.URL || window.webkitURL;
function saveAs(blob, filename) {
var type = blob.type;
var force_saveable_type = 'application/octet-stream';
if (type && type != force_saveable_type) { // 强制下载,而非在浏览器中打开
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
blob = slice.call(blob, 0, blob.size, force_saveable_type);
}
var url = URL.createObjectURL(blob);
var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
save_link.href = url;
save_link.download = filename;
var event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window
});
save_link.dispatchEvent(event);
URL.revokeObjectURL(url);
}
直接使用FileSaver库。也许在某些浏览器需要实现Blob对象可以使用Blob.js。(ps:IE10以下不支持注意兼容性)
var oReq = new XMLHttpRequest();
// The Endpoint of your server
var URLToPDF = "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf";
// Configure XMLHttpRequest
oReq.open("GET", URLToPDF, true);
// Important to use the blob response type
oReq.responseType = "blob";
// When the file request finishes
// Is up to you, the configuration for error events etc.
oReq.onload = function() {
// Once the file is downloaded, open a new window with the PDF
// Remember to allow the POP-UPS in your browser
var file = new Blob([oReq.response], {
type: 'application/pdf'
});
// Generate file download directly in the browser !
saveAs(file, "mypdffilename.pdf");
};
oReq.send();
为了协调事件、用户交互、脚本、呈现、网络等,UA必须使用事件循环的机制。事件循环有两种模式:用于browsing contexts(浏览环境上下文)的循环,以及用于workers的循环。
browsing contexts event loop
个至少含有一个browsing context上下文环境,该事件循环依赖与环境,环境消失的话该事件机制也将销亡.worker processing model
管理事件循环的生命周期.一个事件循环会有一个或者多个任务队列。任务队列是一个有序的list集合,用来处理下面的任务:
microtask
的flag用来检测微任务默认情况为false。来用防止注入事件时调用microtask
检测算法。一个事件循环其实就是在不断运行下面这些步骤的操作:
1. 把oldestTask标记为oldest task. UA可以选择任意任务队列,如果没有选择跳到Microtasks微任务处理
2. 将当前运行任务设置成 oldestTask.
3. 运行 oldestTask.
4. 将事件循环的当前运行任务 置为null
5. 从任务队列中移除oldestTask.
6. Microtasks: 微任务检测点,执行微任务检测.
7. 更新渲染(Update rendering) 主要是浏览器渲染过程不详细展开.
8. worker事件循环判断.
true
null
false
上述概念为whatwg
规范概念,具体实现还得看浏览器厂商。之所以称为事件循环,是因为它经常被用于类似如下的方式来实现:
while (queue.waitForMessage()) {
queue.processNextMessage();
}
由于js的运行环境是单线程的,函数调用会形成了一个栈帧,对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域 ,除此之外JavaScript 运行时还包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈拥有足够内存时,从队列中取出一个消息进行处理。
看图来分析如下:
主线程运行时函数会被压入函数调用栈等待执行,当调用栈中的函数被调用时,任务分发器会根据任务源把对应任务放入不同的event队列中。如webaips产生的回调会被放入回调队列,微任务microtask
会被放入微任务队列。事件循环处理器也就是event loop
按照前面的步骤做loop。
当Node.js启动时会初始化event loop,下图是一个简化版的事件循环执行顺序,大体分为六个阶段。
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
setTimeout
和setInterval
的回调.setImmediate
所产生的回调外的所有回调.node
有时会阻塞这里.setImmediate
的回调.socket.on('close', ...)
.对于定时器和浏览器API类似,需要注意的是定时器何时执行是由poll
阶段决定的,所有精确度不够有时可能被延迟。
poll
阶段主要做两件事:
poll
队列中的事件.如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:
poll
队列为空,将会发生下面情况:
setImmediate
设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的事件.setImmediate
,event loop将阻塞在该阶段等待callbacks加入poll队列.一旦poll队列为空,事件循环将检查timer是否达到时间,如果有一个或者多个timer到达时间事件循环就会返回到timers阶段执行timers的回调.
这个阶段容许立即执行一个回调在poll阶段完成后,如果poll阶段处于空闲状态并且setImmediate
的回调已经入队。那么事件循环将进入check
阶段而不是等待.
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context
https://www.jianshu.com/p/de7aba994523
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
https://cnodejs.org/topic/57d68794cb6f605d360105bf
原文链接: EME WTF?
翻译日期: 2018年6月8日
翻译人员: Gnip
加密媒体扩展提供了一个API,允许web应用与内容保护系统交互,允许播放加密的音频和视频。
EME被设计来保证相同的应用和加密文件可以在任何浏览器环境使用,不管底层保护系统。
前者是通过标准API和规则实现,而后者是由通用加密Common Encryption
概念实现。
EME得名来自对HTMLMediaElement规范的扩展。
作为一个“扩展”意味着浏览器支持EME:如果浏览器不支持加密媒体,它将无法播放加密媒体,但EME对于HTML规范的依赖不是必须需的。
从EME的规范来看:
这个提议扩展HTMLMediaElement提供api来控制播放受保护的内容。
API支持从简单的密钥解密到高价值的视频(给出一个适当的用户代理实现)的情况。
许可/密钥交换是由应用程序控制,促进开发健壮的播放应用程序支持一系列内容解密和保护技术。
本规范没有定义内容保护或数字版权管理系统。相反,它定义了一个通用的API,可以用来发现、选择或者与这些系统以及简单的内容加密系统交互。数字版权管理的实现不需要遵守此规范:只有Clear Key系统需要实现为一个共同的基准。
通用的API支持一组简单的内容加密功能,而把一些应用程序的功能,比如身份验证和授权留给页面作者。这是通过获取由页面分发的内容保护系统的特的消息而不是假设带外之间的通信加密系统或者许可证或其他服务器的通信。
EME的实现使用以下外部组件:
Key System: 内容保护(DRM)机制。EME不定义key system本身,除了clear key(下面详细说明)。
Content Decryption Module (CDM): 客户端软件或硬件机制,来保证播放加密媒体。
和key system一样,EME不定义任何CDMs,但提供了一个接口与CDMs应用程序进行交互。
License (Key) server: 与CDM进行交互,提供密钥解密媒体。与许可服务器交涉是主要责任。
Packaging service: 编码和加密媒体分布/消费
注意应用程序使用EME与一个许可证服务器交互获取密钥来解密,但用户标识和身份验证并不是EME的一部分。
检索键,使媒体播放(可选)之后对用户进行身份验证。
这种服务,例如Netflix必须验证用户在他们的web应用程序:当用户登录应用程序,应用程序决定了用户的身份和特权。
如果多种格式和编解码器都是支持的,MediaSource.isTypeSupported(),或HTMLMediaElement.canPlayType()都可以用来选择正确的一个。然而,CDM可能只支持浏览器支持加密的内容的一个子集。最好是在选择一个格式和编解码器之前,使用一个
MediaKeys
配置。如果应用程序等待加密事件后,然而MediaKeys
显示它无法处理所选的格式/编解码器,它可能是来不及切换而不得不中断播放。
推荐的流程是先通过
MediaKeys
,然后使用MediaKeysSystemAccess.getConfiguration()
获取可靠的配置。
如果只有一个格式/编码器去选择,然后就没有必要
getConfiguration()
. 然而,最好是先建立MediaKeys。等待加密事件的唯一理由是如果没有办法知道内容是否加密,但实际上这是不可能的。
initData
)触发一个加密的事件。MediaKeys
对象与媒体元素关联,首先选择一个可用的密钥系统通过使用navigator.requestMediaKeySystemAccess()
检查哪些系统可用,然后通过MediaKeySystemAccess
为密钥系统创建为一个可用的MediaKeys
对象。注意,MediaKeys对象的初始化应该在第一个加密事件之前。通过选择一个可用的密钥系统,获得许可证服务器的URL是一个独立应用程序。MediaKeys对象代表了所有可用的密钥来解密音频或视频的媒体元素。它代表了CDM实例并提供访问CDM,专门用于创建密钥会话,用于获取密钥从许可证书服务器。MediaKeys
对象被创建,将其分配给媒体元素:setMediaKeys()
分配给HTMLMediaElement
元素,所以可以使用播放期间使用密钥,例如解码的时候。MediaKeySession
通过调用MediaKeys
上的createSession()
方法。MediaKeySession
代表了证书和它密钥的生命周期。CDM
l来生成许可证请求。通过MediaKeySession
调用generateRequest()
方法。CDM
出发一个消息事件:从证书服务器获取密钥的请求。MediaKeySession
对象接收到消息事件然后应用程序通过(例如xhr)发送消息到证书服务器。CDM
使用MediaKeySession
的update()
方法。CDM
解密媒体使用证书中的密钥。一个有效的密钥可能被使用在MediaKeys
关联的媒体元素任何会话中。CDM
会访问有密钥id索引的密钥和策略。浏览器如何知道媒体是加密的?
此信息位于媒体容器文件的元数据中,该文件将采用ISO BMFF或WebM等格式。对于ISO BMFF,这意味着标题元数据,称为保护方案信息框。WebM使用Matroska ContentEncryption元素以及一些WebM特定的添加项。在EME特定的注册表中为每个容器提供准则。
请注意,CDM和许可证服务器之间可能存在多个消息,并且此过程中的所有通信对浏览器和应用程序都是不透明的:消息只能由CDM和许可证服务器理解,但应用程序层可以看到什么类型的消息CDM正在发送。许可证请求包含CDM有效性(和信任关系)以及在生成的许可证中加密内容密钥时使用的密钥。
EME实现本身并不提供解密媒体的方式:它只是为Web应用提供API来与内容解密模块进行交互。
CDM实际做的不是由EME规范定义的,CDM可以处理媒体的解码(解压缩)以及解密。至少从最强大的角度来看,CDM功能有几种可能的选择:
<video>
元素。有多种方式可以为Web应用程序提供CDM:
CDM如何提供并不是由EME规范定义的,但在所有情况下,浏览器负责审核和公开CDM。
EME没有要求特定的关键系统; 在当前的桌面和移动浏览器中,Chrome支持Widevine,IE11支持PlayReady。
可在线使用,Web客户端就可以从许可证服务器获取密钥(包含在许可证中),并使用该密钥来启用内容的解密和播放。
以下代码(根据规范示例进行了调整)显示了应用程序如何选择适当的密钥系统并从许可证服务器获取密钥。
var video = document.querySelector('video');
var config = [{initDataTypes: ['webm'],
videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}]}];
if (!video.mediaKeys) {
navigator.requestMediaKeySystemAccess('org.w3.clearkey',
config).then(
function(keySystemAccess) {
var promise = keySystemAccess.createMediaKeys();
promise.catch(
console.error.bind(console, 'Unable to create MediaKeys')
);
promise.then(
function(createdMediaKeys) {
return video.setMediaKeys(createdMediaKeys);
}
).catch(
console.error.bind(console, 'Unable to set MediaKeys')
);
promise.then(
function(createdMediaKeys) {
var initData = new Uint8Array([...]);
var keySession = createdMediaKeys.createSession();
keySession.addEventListener('message', handleMessage,
false);
return keySession.generateRequest('webm', initData);
}
).catch(
console.error.bind(console,
'Unable to create or initialize key session')
);
}
);
}
function handleMessage(event) {
var keySession = event.target;
var license = new Uint8Array([...]);
keySession.update(license).catch(
console.error.bind(console, 'update() failed')
);
}
通用加密解决方案允许内容提供商对每个容器/编解码器的内容进行加密和打包,并将其与各种关键系统,CDM和客户端一起使用:即支持通用加密的任何CDM。例如,使用Playready打包的视频可以使用Widevine CDM在浏览器中播放,从Widevine许可证服务器获取密钥。
这与传统的解决方案形成鲜明对比,传统解决方案只能使用完整的垂直堆栈,包括通常还包含应用程序运行时的单个客户端。
通用加密(CENC)是ISO标准,用于定义ISO BMFF的保护方案; 类似的概念适用于WebM。
尽管EME没有定义DRM功能,但该规范目前要求所有支持EME的浏览器必须实现Clear Key。使用这个系统,媒体可以用一个密钥加密,然后通过提供该密钥简单地回放。清除密钥可以内置到浏览器中:它不需要使用单独的解密模块。
虽然不太可能用于许多类型的商业内容,但Clear Key可在支持EME的所有浏览器中完全互操作。对于测试EME实现和使用EME的应用程序,无需从许可证服务器请求内容密钥也很方便。simpl.info/ck上有一个简单的Clear Key示例。下面是代码的,和上述步骤相似,虽然没有许可证服务器的交互。
// Define a key: hardcoded in this example
// – this corresponds to the key used for encryption
var KEY = new Uint8Array([
0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
]);
var config = [{
initDataTypes: ['webm'],
videoCapabilities: [{
contentType: 'video/webm; codecs="vp8"'
}]
}];
var video = document.querySelector('video');
video.addEventListener('encrypted', handleEncrypted, false);
navigator.requestMediaKeySystemAccess('org.w3.clearkey', config).then(
function(keySystemAccess) {
return keySystemAccess.createMediaKeys();
}
).then(
function(createdMediaKeys) {
return video.setMediaKeys(createdMediaKeys);
}
).catch(
function(error) {
console.error('Failed to set up MediaKeys', error);
}
);
function handleEncrypted(event) {
var session = video.mediaKeys.createSession();
session.addEventListener('message', handleMessage, false);
session.generateRequest(event.initDataType, event.initData).catch(
function(error) {
console.error('Failed to generate a license request', error);
}
);
}
function handleMessage(event) {
// If you had a license server, you would make an asynchronous XMLHttpRequest
// with event.message as the body. The response from the server, as a
// Uint8Array, would then be passed to session.update().
// Instead, we will generate the license synchronously on the client, using
// the hard-coded KEY at the top.
var license = generateLicense(event.message);
var session = event.target;
session.update(license).catch(
function(error) {
console.error('Failed to update the session', error);
}
);
}
// Convert Uint8Array into base64 using base64url alphabet, without padding.
function toBase64(u8arr) {
return btoa(String.fromCharCode.apply(null, u8arr)).
replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}
// This takes the place of a license server.
// kids is an array of base64-encoded key IDs
// keys is an array of base64-encoded keys
function generateLicense(message) {
// Parse the clearkey license request.
var request = JSON.parse(new TextDecoder().decode(message));
// We only know one key, so there should only be one key ID.
// A real license server could easily serve multiple keys.
console.assert(request.kids.length === 1);
var keyObj = {
kty: 'oct',
alg: 'A128KW',
kid: request.kids[0],
k: toBase64(KEY)
};
return new TextEncoder().encode(JSON.stringify({
keys: [keyObj]
}));
}
要测试此代码,您需要加密的视频才能播放。根据webm_crypt说明,可以为WebM完成对Clear Key使用的视频加密。商业服务也是可用的(至少对于ISO BMFF / MP4)并且正在开发其他解决方案。
HTMLMediaElement是简约和美好的。
我们可以简单地通过提供一个src URL来加载,解码和播放媒体:
<video src = 'foo.webm' > </ video>
The Media Source API
是HTMLMediaElement的扩展,通过允许JavaScript构建用于从视频“块”进行播放的流,实现对媒体源的更精细控制。这反过来又使诸如自适应流式传输和时移的技术成为可能。
为什么MSE对EME很重要?因为除了分发受保护的内容之外,商业内容提供商必须能够根据网络条件和其他要求调整内容交付。例如,Netflix随着网络条件的变化而动态改变码流比特率。EME适用于MSE实施提供的媒体流的回放,就像通过src属性提供的媒体一样。
如何分块和播放以不同比特率编码的媒体?请参阅下面的DASH部分。
您可以在simpl.info/mse中查看MSE的实际操作; 就本示例而言,使用File API
将WebM视频分成五个块。在生产应用程序中,视频块将通过Ajax检索。
首先创建一个SourceBuffer:
var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
然后通过使用appendBuffer()
方法附加每个块,将整个电影“流式传输”到视频元素:
reader.onload = function (e) {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
if (i === NUM_CHUNKS - 1) {
mediaSource.endOfStream();
} else {
if (video.paused) {
// start playing after first chunk is appended
video.play();
}
readChunk_(++i);
}
};
在HTML5 Rocks文章中了解有关MSE的更多信息。
多设备,多平台,移动 - 无论您怎么称呼它,Web都经常在可变连接条件下体验。动态的自适应交付对于应对多设备领域的带宽限制和可变性至关重要。
DASH(也称为MPEG-DASH)旨在在片状世界中实现尽可能最佳的媒体传输,以实现流媒体和下载。其他一些技术可以做类似的事情 - 例如Apple的HTTP实时流媒体(HLS)和微软的平滑流媒体 - 但DASH是通过基于开放标准的HTTP进行自适应比特率流传输的唯一方法。DASH已被YouTube等网站使用。
这与EME和MSE有什么关系?基于MSE的DASH实现可以解析manifest,以适当的比特率下载视频片段,并在饥饿时将它们提供给视频元素 - 使用现有的HTTP基础架构。
换句话说,DASH使商业内容提供商能够对受保护内容进行自适应流式传输。
DASH做什么:
BBC已经开始使用DASH提供测试流:
媒体以不同比特率编码多次。每种编码称为表示。这些被分成许多媒体分部。客户端通过从HTTP请求中按顺序请求分段来播放程序。表示可以分组为包含等同内容的表示的适应集。如果客户希望改变比特率,它可以从当前适配集合中选择一种替代方案,并开始从该表示中请求分段。内容以这种方式进行编码,以便客户端可以轻松地进行切换。除了一些媒体分部之外,一个表示通常还有一个初始化分段。这可以被认为是一个标题,包含关于编码,帧大小等的信息。
总结:
作为视频分割过程的一部分,以编程方式构建称为媒体演示描述(MPD)的XML清单。这描述了适应集和表示形式,带有持续时间和URL。MPD看起来像这样:
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" mediaPresentationDuration="PT0H3M1.63S" minBufferTime="PT1.5S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
type="static">
<Period duration="PT0H3M1.63S" start="PT0S">
<AdaptationSet>
<ContentComponent contentType="video" id="1" />
<Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
<BaseURL>car-20120827-89.mp4</BaseURL>
<SegmentBase indexRange="674-1149">
<Initialization range="0-673" />
</SegmentBase>
</Representation>
<Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
<BaseURL>car-20120827-88.mp4</BaseURL>
<SegmentBase indexRange="708-1183">
<Initialization range="0-707" />
</SegmentBase>
</Representation>
…
</AdaptationSet>
</Period>
</MPD>
(此XML取自用于YouTube DASH演示播放器的.mpd文件。)
根据DASH规范,MPD文件理论上可以用作src视频。然而,为了给予网络开发者更多的灵活性,浏览器厂商选择使用MSE(例如dash.js)将DASH支持留给JavaScript库。在JavaScript中实现DASH允许自适应算法在不需要浏览器更新的情况下发展。使用MSE还可以实现替代清单格式和传送机制,而无需更改浏览器。Google的Shaka Player实现了一个支持EME的DASH客户端。
Mozilla开发者网络有关于如何使用WebM工具和FFmpeg来分割视频和构建MPD的说明。
利用网络提供付费视频和音频的速度正在加快。看起来,每个新设备,无论是平板电脑,游戏机,连接电视还是机顶盒,都能够通过HTTP从主要内容提供商流媒体。目前,超过85%的移动和桌面浏览器支持
简记 | 描述 |
---|---|
gdb program [core] | 调试core文件 |
b [file:] function | 设置断点在函数上 |
r run简写 | 运行你的程序 |
bt backtrace | 显示程序栈 |
p expr | 打印显示表达式的值 |
c continue | 继续运行程序 |
n next | 下一行 跳过函数调用 |
s step | 下一行 步入函数调用 |
命令 | 描述 |
---|---|
gdb | 开启gdb 没有debug文件 |
gdb program | 开始调试可执行文件 |
gdb program core dump file | 调试core是程序非法执行后core dump后产生的文件 |
gdb --help | 命令行帮助选项 |
命令 | 描述 |
---|---|
quit q or EOF | 退出GDB; eg C-d |
INTERRUPT | 终止当前命令,eg C-c |
命令 | 描述 |
---|---|
help | 列出命令的类型 |
help class | 命令类型的描述 |
help command | 命令描述 |
命令 | 描述 |
---|---|
run arglist | 使用arglist开始程序 |
run | 使用当前参数列表开始程序 |
run outf | 使用input output开始程序 |
kill | 结束运行程序 |
tty dev | 使用dev作为标准输入输出为下一次的run |
set args arglist | 为下一次run指定arglist |
set args | 指定空参数列表 |
show args | 显示参数列表 |
show args var | 显示环境中var的值 |
set env var string | 设置环境变量var=string |
unset env var | 移除环境变量var |
命令 | 描述 |
---|---|
cd dir | 改变当前目录到dir |
pwd | 打印当前目录 |
make | 调用 make |
shell cmd | 执行shell 命令串 |
命令 | 描述 |
---|---|
break [file:] line | 设置断点在file中line行处 |
b [file:] line | eg: break mian.c :37. |
break [file:] func | 设置断点在file中func处 |
break +offset or -offset | 设置断点在offset行距离当前停止 |
break *addr | 在地址addr处设置断点 |
break...if expr | 设置条件断点在expr不为0的时候 |
tbreak ... | 临时断点 在到达后无效 |
rbreak [file:]regex | 设置在所有函数匹配 regex在file中 |
watch expr | 设置一个观察点对表达式expr |
catch event | 设置在event上能够被catch,throw,exec,fork,vfork,load,or unload. |
info break | 显示定义的断点 |
info watch | 显示定义的观察点 |
clear | 删除调断点在下一个描述上 |
clear [file:]fun | 删除在fun()入口的断点 |
clear [file: ]line | 清除第line行的断点 |
delete [n] | 删除断点n[或者断点n] |
disable [n] | 暂停第n个断点 |
enable [n] | 开启第n个断点 |
命令 | 描述 |
---|---|
continue [count] | 继续执行,到下一个断点处或运行结束 |
c [count] | continue简写 |
step [count] | 单步调试如果有函数调用,则进入函数 |
s [count] | step简写 |
stepi [count] | 单步执行一条机器指令 |
si [count] | stepi 简写 |
next [count] | 执行下一行包括函数调用 |
n [count] | next 简写 |
nexti [count] | 执行下一条机器指令而不是原始行 |
ni [count] | nexti 简写 |
until [location] | 运行至某行,不仅仅用来跳出循环 |
finish | 运行程序直到当前函数完成返回,并打印函数返回时的堆栈地址返回值及参数值 |
jump line | 重新执行在指定的行数或地址 |
jump* address |
命令 | 描述 |
---|---|
print [/f] [expr] | 显示表达式expr的值 |
p [/f] [expr] | 格式化f 详细 |
x [N u f] expr | 查看内存的 /后可以指定格式化显示 |
whatis | 查询变量或函数 |
命令 | 描述 |
---|---|
dir names | 添加目录名names到源路径 |
dir | 清理源路径 |
show dir | 显示当前源路径 |
whatis | 查询变量或函数 |
list | 显示下十行源代码或者将接着上一次 list 命令的输出下边的内容 |
list - | 显示前十行 |
list lines | 将显示当前文件以lines为中心的前后10行代码 |
info line num | 显示编译代码在num行处开始结束地址 |
info source | 显示源文件的名字 |
info sources | 显示所有源文件在使用中 |
forw regex | 搜索regex在下面源码中 |
http://www.yolinux.com/TUTORIALS/GDB-Commands.html
https://sourceware.org/gdb/onlinedocs/gdb/
http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html
我们都知道JavaScript是单线程的,这意味着任何两句代码都不能同时运行,它们得一个接一个来。在浏览器中,JavaScript 和其他任务共享一个线程,不同的浏览器略有差异,但大体上这些和 JavaScript共享线程的任务包括重绘、更新样式、用户交互等,所有这些任务操作都会阻塞其他任务。
作为人类,你是多线程的。你可以用多个手指同时敲键盘,也可以一边开车一遍电话。唯一的全局阻塞函数是打喷嚏,打喷嚏期间所有其他事务都会暂停。很烦人对么?尤其是当你开着车打着电话的时候。我们都不喜欢这样打喷嚏的代码。尤其是在浏览器环境中往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:
"同步模式"就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
而在异步模式中Promise的出现无疑是一件让人欢呼雀跃的事,这是为什么呢?
一个promise代表了异步操作的最终结果,promise交互的主要方法是通过.then
方法。Promise对象其实是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
Promise对象有以下几种状态:
这里要稍微拓展一点小知识,不知道你有没有听过薛定谔的猫。大体的意思是说在一个盒子里有一只猫,以及少量放射性物质。之后,有50%的概率放射性物质将会衰变并释放出毒气杀死这只猫,同时有50%的概率放射性物质不会衰变而猫将活下来。你永远的不会知道猫是死还是活在盒子未被打开前。这一点和promise的机制很相似,也就是说在异步回调返回之前,程序是无法知道结果是成功还是失败,因此我们给出了相应的预处理。
pending状态的promise对象既可转换为带着一个成功值的fulfilled 状态,也可变为带着一个失败信息的 rejected 状态。当状态发生转换时,promise.then
绑定的方法(函数句柄)就会被调用。(当绑定方法时,如果 promise对象已经处于 fulfilled 或 rejected 状态,那么相应的方法将会被立刻调用, 所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。)
因为Promise.prototype.then
和 Promise.prototype.catch
方法返回 promises
对象, 所以它们可以被链式调用—— 一种被称为 composition 的操作。
'use strict';
// A-> $http function is implemented in order to follow the standard Adapter pattern
function $http(url){
// A small example of object
var core = {
// Method that performs the ajax request
ajax : function (method, url, args) {
// Creating a promise
var promise = new Promise( function (resolve, reject) {
// Instantiates the XMLHttpRequest
var client = new XMLHttpRequest();
var uri = url;
if (args && (method === 'POST' || method === 'PUT')) {
uri += '?';
var argcount = 0;
for (var key in args) {
if (args.hasOwnProperty(key)) {
if (argcount++) {
uri += '&';
}
uri += encodeURIComponent(key) + '=' + encodeURIComponent(args[key]);
}
}
}
client.open(method, uri);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx
resolve(this.response);
} else {
// Performs the function "reject" when this.status is different than 2xx
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
// Return the promise
return promise;
}
};
// Adapter pattern
return {
'get' : function(args) {
return core.ajax('GET', url, args);
},
'post' : function(args) {
return core.ajax('POST', url, args);
},
'put' : function(args) {
return core.ajax('PUT', url, args);
},
'delete' : function(args) {
return core.ajax('DELETE', url, args);
}
};
};
// B-> Here you define its functions and its payload
var mdnAPI = 'https://developer.mozilla.org/en-US/search.json';
var payload = {
'topic' : 'js',
'q' : 'Promise'
};
var callback = {
success : function(data){
console.log(1, 'success', JSON.parse(data));
},
error : function(data){
console.log(2, 'error', JSON.parse(data));
}
};
// End B
// Executes the method call
$http(mdnAPI)
.get(payload)
.then(callback.success)
.catch(callback.error);
// Executes the method call but an alternative way (1) to handle Promise Reject case
$http(mdnAPI)
.get(payload)
.then(callback.success, callback.error);
// Executes the method call but an alternative way (2) to handle Promise Reject case
$http(mdnAPI)
.get(payload)
.then(callback.success)
.then(undefined, callback.error);
上面代码其实是javascript的一种设计模式,详细可阅读Learning JavaScript Design Patterns。适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。适配器的别名是包装器(wrapper),这是一个相对简单的模式。通过适配器模式将传统的xhr请求封装成restful风格然后返回Promise进一步方便链式调用。
See the Pen promise-test by Gnipbao (@Gnipbao) on CodePen.
如果你想系统的学习promsie
的话建议你去看promise迷你书写的相当不错。
最后推荐一个不错的promise
库ES6-Promise。
利用开启隐身模式的chrome浏览器不能通过 requestFileSystem Api 访问系统文件的特点作为hack依据进行检测。
/**
* Determine wheter the incognito mode of Google Chrome is available or not.
*
* @param callback Anonymous function executed when the availability of the incognito mode has been checked.
*/
function isIncognito(callback){
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
callback(false);
} else {
fs(window.TEMPORARY,
100,
callback.bind(undefined, false),
callback.bind(undefined, true)
);
}
}
isIncognito(function(itIs){
if(itIs){
console.log("我是隐身模式");
}else{
console.log("我不是隐身模式");
}
});
JavaScript本身不是一种模块化语言,设计者在创造JavaScript之初应该也没有想到这么一个脚本语言的作用领域会越来越大。以前一个页面的JS代码再多也不会多到哪儿去,而现在随着越来越多的JavaScript库和框架的出现,Single-page App的流行以及Node.js的迅猛发展,如果我们还不对自己的JS代码进行一些模块化的组织的话,开发过程会越来越困难,运行性能也会越来越低。因此,了解JS模块化编程是非常重要的。
可以看看requirejs官方的解释说的非常详细
why web modules
UMD是AMD和CommonJS的糅合,AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
下面看下一些经典库的封装基本都是基于UMD方式的
(function(global, factory) {
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document ?
factory(global, true) :
function(w) {
if (!w.document) {
throw new Error("jQuery requires a window with a document");
}
return factory(w);
};
} else {
factory(global);
}
// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function(window, noGlobal) {
//........
//amd
if (typeof define === "function" && define.amd) {
define("jquery", [], function() {
return jQuery;
});
}
var
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$;
jQuery.noConflict = function(deep) {
if (window.$ === jQuery) {
window.$ = _$;
}
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
return jQuery;
};
// Expose jQuery and $ identifiers, even in AMD
// and CommonJS for browser emulators (#13566)
if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
//return obj
return jQuery;
}))
jquery框架使用的是一个自执行函数包裹
(function(xx,oo){
//...
}(xx,oo))
;(function (global, factory) {
//三元表达式进行commonjs amd的检测
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
global.moment = factory()
}(this, (function () {
'use strict';
//工厂函数
})));
moment和jquery的实现方式几乎一样 只是把amd cmd的检测放到了一起做判断,jquery是在工厂函数中进行amd的判断 命名冲突检测 全局暴露等操作;
成为 Emacs高手 像神一样使用编辑器
进入不弹出窗口: emacs -nw aa.txt
退出emacs : C-x C-c
挂起退出:C-z
打开文件: C-x C-f
保存文件:C-x C-s
保存所有:C-x s
插入另一个文件的内容到当前buffer:C-x i
替换文件:C-x C-v
写入buffer:C-x C-w
切换buffer的只读状态:C-x C-q
系统帮助:C-h t
移除帮助窗口:C-x 1
滚动帮助窗口:C-M-v
中断部分类型或者退出命令:C-g
恢复由于系统崩溃丢失的文件:M-x recover-session
撤销:C-x u, C-_ or C-/
恢复buffer内容:M-x revert-buffer
向前搜索:C-s
向后搜索:C-r
正则搜索:C-M-s
反正则搜索:C-M-r
选择上一个搜索内容:M-p
选择下一个搜索内容:M-n
退出搜索:RET
撤销上一个字符:DEL
中断当前搜索:C-g
跳到下一个再按一次C-s 或者C-r
移动实体 | 后 | 前 |
---|---|---|
字符 | C-b | C-f |
单词 | M-b | M-f |
行 | C-p | C-n |
行首或者尾 | C-a | C-e |
句子 | M-a | M-e |
段落 | M-{ | M-} |
页 | C-x [ | C-x ] |
语法单位 | C-M-b | C-M-f |
函数 | C-M-a | C-M-e |
buffer | M-< | M-> |
滚到下一屏幕:C-v
滚到上一屏幕:M-v
滚到左边:C-x <
滚到右边:C-x >
跳到行:M-g g
跳到字符:M-g c
移动实体 | 向后 | 向前 |
---|---|---|
字符 | DEL | C-d |
单词 | M-DEL | M-d |
行 | M-0 C-k | C-k |
句子 | C-x DEL | M-k |
语法单位 | M-- C-M-k | C-M-k |
删除一个区域: C-w
复制区域: M-w
粘贴内容到当前光标位置: C-y
切换剪贴板内容: M-y
替换前询问,y替换,n不替换: M-%
正则替换 : M-x replace-string RET string RET newString RET
水平开一个窗口: C-x 2
垂直开一个窗口: C-x 3
在窗口间切换当前窗口:C-x o
关闭当前窗口以外的其他窗口:C-x 1
关闭当前窗口:C-x 0
剪切这个矩形区域: C-x r k
复制选定的矩形区域: C-x r M-w
删除选定的矩形区域: C-x r d
将剪切的矩形区域粘贴到选定的矩形区域 :C-x r y
在当前选定区域前插入选定区域大小全是空格的区域: C-x r o
在当前选定区域前插入行号,行号从1开始: C-x r N
将当前选定区域替换成空格区域:C-x r c
将区域中的每一行替换成string:C-x r t string RET
在矩形区域的每一行前插入string :M-x string-insert-rectangle RET string RET
自2003年以来,H.264一直是最先进并被最广泛部署的视频压缩格式,也催生了HDTV、蓝光DVD、互联网视频网站(如YouTube,Twitch)等许许多多成功的商业产品。但是目前基于H.264协议的编码器已经达到了它们压缩性能的极限。特别是对于高清分辨率(1080p60)游戏内容的实时编码,这些编码器已经江郎才尽,耗尽了所有可挖掘的技术潜力。然而与此同时,新涌现出的下一代视频标准,VP9,HEVC和AV1。本文主要介绍VP9相对H.264对比的可实施性。
具体可以查看wiki
许多浏览器都支持VP9视频格式,图中可以看出Chrome和Firefox的支持程度比较好。截止2018年6月,约有4/5的浏览器(包括移动设备)支持WebM封装容器和VP9视频编码,例如Chromium、Chrome、Microsoft Edge、Firefox、Opera等浏览器都内置了VP9解码器,可在HTML5播放器中播放VP9影像格式。Windows 10操作系统也内置了WebM分离器和VP9解码器。
参考paper《Performance Comparison of H.265/MPEG-HEVC, VP9, andH.264/MPEG-AVC Encoders》
下表显示了HM的参数配置。
下表显示了VP9和x264的参数配置。
使用的测试序列如下表所示
实验结果如下图所示
下表显示了HEVC在同等质量的前提下(以PSNR为依据),相对于VP9和x264节约的码率。下表显示了所有序列的情况。总体来说HEVC相对于VP9节约了41.9%,HEVC相对于x264节约了38.9%。
下表显示了三种编码器整体性能的比较。表中百分比数字的意义是:同等视频质量的前提下,该列所属的编码器相对于该行所属的编码器节约的码率,如果为负值,则代表反而消耗了更多的码率。例如,同等质量的前提下,x264相对于VP9节约了8.4%的码率。
同等视频质量的前提条件下,编码消耗时间对比如下表所示。可以看出,VP9编码时间大约是x264的130倍。HEVC编码时间大约是VP9的7倍。
来源于大量视频数据测试
具体可以看 视频
基本上能得出:与x264相比,x265和vp9具有出色的压缩性能,特别是在更高的分辨率下,比特率节省高达50%。x265几乎在所有分辨率和质量指标上都优于vp9,但性能差距在1080p时缩小(甚至反正)
总体上来看vp9的优势有: vp9在编码效率方面优于x264 在1080p以上编码的压缩效率上可以实现至少25%的码率节省, 压缩性能更高,运作起来效率更高,比起x264要高出50%,用户使用原来一半的带宽就可以观看网络视频,没有专利费。
1.播放格式
点播实现流式播放具体可以参考webm项目官方文档给出的4种格式。
2.MSE
MSE支持测试demo
3.其他
webm 没有ts类似.m3u8的描述文件需要自己实现。可参考youtobe方案
http://en.wikipedia.org/wiki/VP9
https://www.texpion.com/2018/07/av1-vs-vp9-vs-avc-h264-vs-hevc-h265-1-lossless.html
https://blog.csdn.net/owen7500/article/details/47334929
https://blog.csdn.net/leixiaohua1020/article/details/11713041
https://blog.csdn.net/leixiaohua1020/article/details/19014955
由于之前业务需求需要对服务器返回的zip包进行解压、解密。 服务器使用的是Java DES加密算法需要前端来进行解密。目前浏览器端解密库里CryptoJS算得上是比较通用的库了。而且CryptoJS加密解密算法很多支持度比较广并且网上资料丰富可以有助与我们快速完成相关业务。下面以DES加密解密为例给大家介绍一下CryptoJS加密解密一些常见问题。
总结: ArrayBuffer对象代表原始的二进制数据,TypedArray对象代表确定类型的二进制数据,DataView对象代表不确定类型的二进制数据。它们支持的数据类型一共有9种。WordArray对象是CryptoJS内部实现的一种二进制数据结构用32位数组表示。具体实现感兴趣可以参考WordArray源码
对于字符串加解密比较简单我们可以直接看官方API搜DES加密即可下面给出封装代码
// encrypt string
function encryptByDES(message, key) {
let keyHex = CryptoJS.enc.Utf8.parse(key);
let encrypted = CryptoJS.DES.encrypt(message, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
// decrypt string
function decryptByDES(ciphertext, key) {
let keyHex = CryptoJS.enc.Utf8.parse(key);
// direct decrypt ciphertext
let decrypted = CryptoJS.DES.decrypt(
{
ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
},
keyHex,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
考虑到平时项目中大部分是加载一个具体的文件流进行加解密这种场景比较多所以重点讲一下。这部分官方是没有给出说明需要自己摸索。
由于CryptoJS中大量使用WordArray所以在加密解密文件流的时候需要和uInt8Array之间进行转换 不然解码出来可能是乱码。
下面封装的工具可以很方便提供转换,可以在引入CryptoJS时候对CryptoJS.enc做一个增强
// enchance for Cryptojs enc u8array
CryptoJS.enc.u8array = {
/**
* Converts a word array to a Uint8Array.
*
* @param {WordArray} wordArray The word array.
*
* @return {Uint8Array} The Uint8Array.
*
* @static
*
* @example
*
* let u8arr = CryptoJS.enc.u8array.stringify(wordArray);
*/
stringify: function(wordArray) {
// Shortcuts
let words = wordArray.words;
let sigBytes = wordArray.sigBytes;
// Convert
let u8 = new Uint8Array(sigBytes);
for (let i = 0; i < sigBytes; i++) {
let byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
u8[i] = byte;
}
return u8;
},
/**
* Converts a Uint8Array to a word array.
*
* @param {string} u8Str The Uint8Array.
*
* @return {WordArray} The word array.
*
* @static
*
* @example
*
* let wordArray = CryptoJS.enc.u8array.parse(u8arr);
*/
parse: function(u8arr) {
// Shortcut
let len = u8arr.length;
// Convert
let words = [];
for (let i = 0; i < len; i++) {
words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8);
}
return CryptoJS.lib.WordArray.create(words, len);
}
};
// encrypt arraybuffer
let _encrypt = (key, u8array) => {
let keyHex = CryptoJS.enc.Utf8.parse(key);
let encryptedWordArray = CryptoJS.enc.u8array.parse(u8array);
let encrypted = CryptoJS.DES.encrypt(
encryptedWordArray
keyHex,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return encrypted;
};
// decrypt arraybuffer from file
let _decrypt = (u8array, key) => {
let keyHex = CryptoJS.enc.Utf8.parse(key);
let decryptedWordArray = CryptoJS.enc.u8array.parse(u8array);
let decrypted = CryptoJS.DES.decrypt(
decryptedWordArray.toString(CryptoJS.enc.Base64),
keyHex,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
};
codesandbox
建议copy代码在本地运行 由于codesandbox地址saveAs会有问题。
https://cryptojs.gitbook.io/docs/
https://stuk.github.io/jszip/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
http://www.appblog.cn/2019/07/01/CryptoJS%E4%B8%ADWordArray/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.