Git Product home page Git Product logo

iblog's Introduction

Hi there 👋

Anurag's github stats

iblog's People

Contributors

dependabot[bot] avatar gnipbao avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iblog's Issues

JavaScript函数式编程简介

fp1

目录

  • 函数式编程概念
    • 编程范式
    • 纯函数 (Purity)
  • 高阶函数
  • 由函数构建函数
    • 函数组合 (Function Composition)
    • 柯里化(Currying)
    • 部分应用 (Partial Application)
  • 递归
    • 相互递归
    • 尾递归(Tail Calls)
    • 蹦床原理(Trampolines)
  • 流行的类库

什么是函数式编程

  • 编程范式
  • 面向数学的抽象(面向函数的编程)
  • 函数作为一等公民(可以像任何其他数据类型一样对待)

Functional Programming Hurts?

fp0

纯函数

  • 此函数在相同的输入值时,总是产生相同的输出。函数的输出和当前运行环境的上下文状态无关。
  • 此函数运行过程不影响运行环境,比如不会触发事件、更改环境中的对象、终端输出值等。
  • 相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

函数式编程的好处

  • 引用透明(Referential transparency)
    • 一个表达式能够被它的值替代而不改变程序的行为成为引用透明。
  • No Side Effect(基于状态不可变性)
  • 易于测试(上下文无关)
  • 可并行计算(时序无关,即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置)
  • bug自限性(问题不会扩散)

高价函数(HOF)

  • 定义
    • 以一个函数作为参数
    • 以一个函数作为返回值
  • 应用
    • ajax异步回调
    • Array的一些方法sort, map,filter,reduce等
    • 惰性加载函数,函数节流,实现AOP等

惰性加载函数

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);
};

once

demo

throttle(截流函数)

demo

函数组合(compose)

接收多个函数作为参数,从右到左,一个函数的输入为另一个函数的输出。
compose

compose

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 函数可以自动完成柯里化。

demo

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]
//显示控制接受固定以及可选的参数的函数行为

curryN

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){
    //...
}

部分应用(Partial Application)

部分应用也叫偏函数应用是指使用一个函数并将其应用一个或多个参数,但不是全部参数,在这个过程中创建一个新函数。

// 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);
     };
 }

Partial

// 创建偏函数,固定一些参数
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

对比

fp3

递归

  • 如果一个函数在内部调用自身本身,这个函数就是递归函数
  • 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
  • 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

相互递归

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

尾调用(Tail Calls)

指某个函数的最后一步是调用另一个函数

function f(x){
  return g(x);
}
// case1 不属于
function f(x){
  let y = g(x);
  return y;
}
// case2 不属于
function f(x){
  return g(x) + 1;
}

尾递归


  • 尾调用自身,就称为尾递归。
  • 解决递归调用栈溢出的方法是通过尾递归优化(技术上)
  • 在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
  • ES6标准规定了 尾调用不会创建额外的调用帧。在严格模式下 尾调用不会造成调用栈溢出。
  • 递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要.

求自然数阶乘

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;
}

trampoline

demo

一些资料

黑客精神

  1. 世上仍有大量迷人的事情等待解决
  2. 同样的问题不应被重复处理两次
  3. 拒绝重复和沉闷的事情
  4. 自由**
  5. 精神不能代替能力

黑客们解决问题,建设事物,他们崇尚自由和无私的双向帮助。要被他人承认是一名黑客,你必须表现得你具备了这样的态度。而要表现得你具备了这种态度,你必须彻彻底底的坚持它。

如果你认为培养黑客的态度只是一条在这个文化圈中得到认同的路子,那就错了。成为具备这种素质的人对 ¸非常重要 —— 使你保持学习和成为黑客的自发性。正如所有创造性艺术一样,成为大师的最有效途径就是效仿大师的精神——不仅从理念上,还要从态度上效仿。

或许下面的这首现代禅诗很好的阐述了这个意思:

To follow the path:
沿着这样一条道路:
look to the master,
关注大师,
follow the master,
跟随大师,
walk with the master,
与大师同行,
see through the master,
洞察大师,
become the master.
成为大师。

如果你想成为一名黑客,反复阅读以下内容直到你相信它们:

1. 世上仍有大量迷人的事情等待解决

作为一名黑客可以享受很多乐趣,同时需要付出相当多的努力。努力需要动力。成功的运动员从锻炼身体、超越身体极限中获得精神愉悦。类似的,作为一名黑客,你可以从解决问题、磨练技术和锻炼智力中获得乐趣。

如果你天生不是这样的人,那你需要设法变成这样的人以使你能够成为一名黑客。否则你将会发现你的精力会被诸如性、金钱、社会上的虚名之类让人分心的东西所消磨掉。

(你还需要对自己的学习能力树立信心——相信金关当你对某一问题了解得不多,只要你能解决其中一部分,并从中学习,你可以解决其他的部分——直到解决它。)

2. 同样的问题不应被重复处理两次

创造性的智慧是非常有价值且稀缺的资源。它们不应当被浪费在重复发明轮子上,世上仍有大量迷人的新问题等着解决。

作为一名黑客,你应该坚信其他黑客的时间非常宝贵——所以你有义务共享信息,解决问题之后公布方案,这样其他人可以去解决新的问题,而不是忙于应付旧问题。

注意,“同一个问题不应该被重复处理两次”并不是说你必须认为所有已有方案都是最优的,或每个问题只有唯一的解决方案。通常我们从一个问题的最初解决方案中能够学习到很多东西。这很好,并且对于我们思考如何能做得更好来说,这通常是必要的。我们反对的是人为的技术、法律上的,或者机构性的设置障碍(例如闭源软件),使得一个好的方案不能被重复使用,逼得人们重造轮子。

(你不必认为你必须将 所有 你的创造发明都公布出去,虽然这样做的黑客将会赢得大家极度尊重。适当卖一些钱来换取足够的食物、租金和电脑并不违反黑客的价值观。用你的技能来养家餬口甚至致富都可以,只要你在做这些的时候别忘记你是一名黑客。)

3. 拒绝重复和沉闷的事情

黑客(以及富有创造力的所有人)不应当被愚蠢的重复性劳动所困扰,因为这意味着他们并没有在做只有他们才能做的事情——解决新问题。这样的浪费会伤害所有人。因此,无聊和乏味的工作不仅仅是令人不爽,而是罪恶。

作为一个黑客,你应该坚信这一点并尽可能的将枯燥的工作自动化,这不仅仅是为了你自己,也为了其他人(尤其是其他黑客)。

(这里有一个例外。黑客有时会做一些看起来重复或枯燥的事情以进行脑力休息,或以此来锻炼一种技能,或以此获得某种除此以外无法获取的经验。但这是有选择的——有脑子的人不该被强迫做枯燥的事。)

4. 自由**

黑客是天生的反**主义者。 任何能向你发号施令的人能够迫使你停止解决令你着迷的问题。 同时,按照**者的一般思路,他通常会给出一些极端愚昧的理由。因此,不论何处,任何**主义的作法,只要它压迫你和其他黑客,你就要和它斗到底。

(这并非向所有权威挑战。儿童需要监护,罪犯要被看管起来。如果服从命令得到某种东西比起用其他方式得到它更节约时间,黑客可以同意 接受某种形式的权威。但这是一个有限度的,有意的交易;那种权威想要的个人服从不是你应该同意给予的。)

权威喜欢审查和保密。他们不信任自愿的合作和信息共享——他们只喜欢由他们控制的所谓“合作”。因此,作为一个黑客,你得对审查、保密,以及使用武力或欺骗去压迫有行为能力的人们的做法有一种本能的敌意。 同时你要有为此信念斗争的意愿。

5. 精神不能代替能力

作为一个黑客,你必须培养起这些精神。但是仅仅有精神并不能使你成为黑客,也不能使你成为运动健将或摇滚明星。成为一名黑客还需要智力,实践,奉献精神和辛勤工作。

因此,你需要学会有怀疑态度和尊重任何能力。黑客不会为装模作样的人浪费时间,但他们尊重能力——尤其是从事黑客工作的能力,不过任何能力都是好的。很少人能具备的高要求能力尤其好,其中涉及脑力,技巧和专注方面的能力最好。尊重能力,你就会享受到提高自己的能力所带来的乐趣。

Uint8Array to string in JavaScript

Little strings

少量数据使用超出异常

/**
 * 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);
}

Browser implementation

浏览器环境使用TextEncoderTextDecoder 原生方法实现,注意兼容性。

/**
 * 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);
}

Crossplatform method

跨平台依赖性低运行良好

// 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;
}

Large blocks

大量数据异步情况

/**
 * 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);
});

JavaScript中Promise在同步代码中的两种执行方式

1.同步串行执行多个Promise

  1. reduce实现
// 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
})
  1. reduce 和 async实现
// promises
let runPromiseSequence = (tasks) => {
	return tasks.reduce(async (curr, next) => {
		await curr;
		return next();
	}, Promise.resolve());
};
runPromiseSequence.then((ret)=>{
    // todo
})

解析

reduce里的第一个函数相当于一个reducer可以传递4个参数:

  • acc累计器
  • cur当前值
  • idx 当前索引
  • arr 数组

第二个参数可以理解为acc的初始值,在reduce执行时候先会赋值acc初始值initValue也就是一个Promise.resolve(),curr赋值为当前tasks的第一个值也就是数组中第一个异步函数,由于tasks是一个promise异步任务函数 ,这里使用Promise.resolve()来触发执行,这时候会触发acc.then()然后执行curr也就是第一个异步函数执行并继续返回一个promise赋值给acc,curr取下一个异步函数这样就能实现串行执行每个task。
第二种async只不过是将reducer函数使用aysnc函数来实现本质上是类似的只是代码看起来简单很多。
注意reducer中第一个参数其实是一个执行后的结果这里是Promise而非函数。

2.同步并行执行多个Promise

并行比较简单使用Promise.all原生api既可以实现。

3. 简单测试

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);

并行执行

4. 参考

https://github.com/sindresorhus/promise-fun

JavaScript原生数组

说明

数组是类似列表的对象,在原型中提供了一些遍历以及改变其中对象的方法。JavaScript数组的长度及其中元素的类型都是不固定的。因为数组的长度可以随时增长或缩减,所以JavaScript 数组不保证是密集的。通常情况下,这是一些方便的特性;如果这些特性不适用于你的特定使用场景,你可以考虑使用固定类型数组。在JavaScript中数组可以使用数组构造函Array被创建,为了方便一般用中括号[]创建,Arrays继承Object的原型.typeof检测数组的类型会返回object,可是使用[]instanceof Array,会返回true,也就是说在JavaScript中存在类似数组的Array-like的对象objects,例如:strings,arguments对象,arguments对象不是数组的实例,但是它有length属性,它的值和数组的下标属性之间是有关系的并且length是可变的(例如, push, splice, 等) 会改变Arraylength属性。因此它可以像任何数组一样去遍历.这篇文章主要介绍一些Array.prototype中常用的方法,尽可能揭开每一种方法的真面纱。

array

你可以在你的浏览器控制台里面尝试!

目录

  • .forEach循环
  • .some.every检测
  • .join.concat细微差别
  • .pop,.push,.shift,和.unshift模拟堆栈
  • .map映射
  • .filter过滤测试
  • .sort排序
  • .reduce,.reduceRight累加器
  • .slice复制(copy)
  • .splice修改(update)
  • .indexOf查找
  • .reverse逆序

forEeah循环

array.forEach(callback[,thisArg])forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者从未赋值的项将被跳过(但不包括哪些值为 undefined 的项),这个给定的回调函数可以传入三个参数

  • callback 给定执行的回调函数
  • value 数组当前项的值
  • index 当前的索引(或下标)
  • array 数组本身
  • thisArg 可选参数。用来当作callback 函数内this的值的对象。
['_', 't', 'a', 'n', 'i', 'f', ']'].forEach(function (value, index, array) {
	this.push(String.fromCharCode(value.charCodeAt() + index + 2))
}, out = [])

out.join('')
//<-'awesome'

some和every

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指的是可以转换为布尔值的元素.

join和concat

str = arr.join([separator = ',']) join方法将所有的数组元素被转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是undefined 或者null,则会转化成空字符串。

  • separator 可选,用于指定连接每个数组元素的分隔符。分隔符会被转成字符串类型;如果省略的话,默认为一个逗号。如果 seprator 是一个空字符串,那么数组中的所有元素将被直接连接。
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的一个浅复制.对新数组的任何操作都不会对原数组产生影响,反之亦然.原数组中的元素有两种被拷贝的方式:

  • 对象引用(非对象直接量):concat 方法会复制对象引用放到组合的新数组里,原数组和新数组中的对象引用都指向同一个实际的对象,所以,当实际的对象被修改时,两个数组也同时会被修改.
  • 字符串和数字(是原始值,而不是包装原始值的 String 和 Number 对象): concat 方法会复制字符串和数字的值放到新数组里.
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]);

pop,push和shift,unshift模拟堆栈

array.pop() pop方法删除一个数组中的最后一个元素,并且把这个删除掉的元素返回给调用者。pop 被有意设计成具有通用性,该方法可以通过 callapply 方法应用于一个类数组(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 方法并不局限于数组:该方法亦可通过 callapply 作用于对象上。对于不包含 length 属性的对象,将添加一个值为 0 的 length 属性。

arr.unshift(element1, ..., elementN) unshift 方法会在调用它的类数组(array-like)对象的开始位置插入给定的参数。unshift 特意被设计成具有通用性;这个方法能够通过 callapply 方法作用于类似数组的对象上。不过对于没有 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

map

array.map(callback[, thisArg]) map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值组合起来形成一个新数组。callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

  • callback 给定执行的回调函数
  • value 数组当前项的值
  • index 当前的索引(或下标)
  • array 数组本身
  • thisArg 可选参数。用来当作callback 函数内this的值的对象。
    如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象。
    map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。
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)
	}
})

filter

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, '']

sort

arr.sort([compareFunction]) sort()方法对数组的元素做原地的排序,并返回这个数组。 sort 可能不是稳定的。默认按照字符串的Unicode码位点(code point)排序。如果没有指明 compareFunction ,那么元素会被转换为字符串并按照万国码位点顺序排序。例如 "Cherry" 会被排列到 "banana" 之前。当对数字进行排序的时候, 9 会出现在 80 之后,因为他们会先被转换为字符串,而 "80" 比 "9" 要靠前。如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。记 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • 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];
});

reduce和reduceRight

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]

slice

array.slice([begin[,end]]) slice不修改原数组,只会返回一个包含了原数组中提取的部分元素的一个新数组。原数组的元素会按照下述规则拷贝:

  • 对象引用(非对象直接量):slice方法会拷贝对象引用放到新数组里,原数组和新数组中的对象引用都指向同一个实际的对象,所以,当实际的对象被修改时,两个数组也同时会被修改.
  • 字符串和数字(是原始值,而不是包装原始值的 String 和 Number 对象): 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>

splice

array.splice(start, deleteCount[, item1[, item2[, ...]]]) splice() 方法用新元素替换旧元素,以此修改数组的内容。如果添加进数组的元素个数不等于被删除的元素个数,数组的长度会发生相应的改变。

  • 返回值 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
  • 参数
    • start 从数组的哪一位开始修改内容。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位。
    • deleteCount 整数,表示要移除的数组元素的个数。如果 deleteCount 是 0,则不移除元素。这种情况下,至少应添加一个新元素。如果 deleteCount 大于start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。
    • itemN 要添加进数组的元素。如果不指定,则 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]

indexOf

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 

reverse

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中的地位是非常重要的,函数式编程是这门语言的精髓,本文不会在这里针对函数去展开,后面我会详细介绍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. 数组扁平化
//[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
}
  1. 字符串驼峰化
/**
 * 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"));
  1. 字符串反驼峰化
/**
 * 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", "_"));

github如何向开源项目提交pr

  • 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

h5 video在不同平台上差异分析

video标签属性和事件

video标签的属性:

  • src :视频的属性
  • poster:视频封面,没有播放时显示的图片
  • preload:预加载
  • autoplay:自动播放
  • loop:循环播放
  • controls:浏览器自带的控制条
  • width:视频宽度
  • height:视频高度

Video 对象属性:

  • audioTracks: 返回表示可用音频轨道的 AudioTrackList 对象。
  • autoplay: 设置或返回是否在就绪(加载完成)后随即播放视频。
  • buffered: 返回表示视频已缓冲部分的 TimeRanges 对象。
  • controller: 返回表示视频当前媒体控制器的 MediaController 对象。
  • controls: 设置或返回视频是否应该显示控件(比如播放/暂停等)。
  • crossOrigin: 设置或返回视频的 CORS 设置。
  • currentSrc: 返回当前视频的 URL。
  • currentTime: 设置或返回视频中的当前播放位置(以秒计)。
  • defaultMuted: 设置或返回视频默认是否静音。
  • defaultPlaybackRate: 设置或返回视频的默认播放速度。
  • duration: 返回视频的长度(以秒计)。
  • ended: 返回视频的播放是否已结束。
  • error: 返回表示视频错误状态的 MediaError 对象。
  • height: 设置或返回视频的 height 属性的值。
  • loop: 设置或返回视频是否应在结束时再次播放。
  • mediaGroup: 设置或返回视频所属媒介组合的名称。
  • muted: 设置或返回是否关闭声音。
  • networkState: 返回视频的当前网络状态。
  • paused: 设置或返回视频是否暂停。
  • playbackRate: 设置或返回视频播放的速度。
  • played: 返回表示视频已播放部分的 TimeRanges 对象。
  • poster: 设置或返回视频的 poster 属性的值。
  • preload: 设置或返回视频的 preload 属性的值。
  • readyState: 返回视频当前的就绪状态。
  • seekable: 返回表示视频可寻址部分的 TimeRanges 对象。
  • seeking: 返回用户当前是否正在视频中进行查找。
  • src: 设置或返回视频的 src 属性的值。
  • startDate: 返回表示当前时间偏移的 Date 对象。
  • textTracks: 返回表示可用文本轨道的 TextTrackList 对象。
  • videoTracks: 返回表示可用视频轨道的 VideoTrackList 对象。
  • volume: 设置或返回视频的音量。
  • width:设置或返回视频的 width 属性的值。

Video 对象方法:

  • addTextTrack(): 向视频添加新的文本轨道。
  • canPlayType(): 检查浏览器是否能够播放指定的视频类型。
  • load(): 重新加载视频元素。
  • play(): 开始播放视频。
  • pause(): 暂停当前播放的视频。

视频状态Media事件

属性 描述
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()事件继续播放 一致 一致

如何使用JavaScript压缩解压zip包

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));
    });

相关开源库

JSZip
FileSaver

ffmpeg常用命令

FFmpeg是一套开源多媒体视频处理工具集,在音视频流媒体领域有着举足轻重的作用,详细可以看维基百科FFmpeg,本文主要介绍其命令行工具ffmpeg,整理出来一些常用命令方便查找使用。

ffmpeg常用命令

通过ffmpeg --help可以看到ffmpeg常见的命令大概分为6个部分:

  • ffmpeg信息查询部分
  • 公共操作参数部分
  • 文件主要操作参数部分
  • 视频操作参数部分
  • 音频操作参数部分
  • 字幕操作参数部分

1. ffmpeg使用语法

命令使用:
ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件]
参数选项详细见官网

a) 通用选项

-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) 视频选项

-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

c)高级视频选项 -g gop_size 设置图像组大小

-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 包括了模块名和参数,用空格分开

d)音频选项 -ab bitrate 设置音频码率

-ar freq 设置音频采样率

-ac channels 设置通道 缺省为1

-an 不使能音频纪录

-acodec codec 使用codec编解码

e)音频/视频捕获选项

-vd device 设置视频捕获设备。比如/dev/video0

-vc channel 设置视频捕获通道 DV1394专用

-tvstd standard 设置电视标准 NTSC PAL(SECAM)

-dv1394 设置DV1394捕获

-av device 设置音频设备 比如/dev/dsp

f)高级选项

-map file:stream 设置输入流映射

-debug 打印特定调试信息

-benchmark 为基准测试加入时间

-hex 倾倒每一个输入包

-bitexact 仅使用位精确算法 用于编解码测试

-ps size 设置包大小,以bits为单位

-re 以本地帧频读数据,主要用于模拟捕获设备

-loop 循环输入流。只工作于图像流,用于ffserver测试

2.常用场景

分离视频音频流

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的图片

把视频的前30帧转换成一个Animated Gif

ffmpeg -i input_file -vframes 30 -y -f gif output.gif

在视频的第8.01秒出截取230x240的缩略图

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秒截一张图

从视频中生成GIF图片

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

切分视频并生成M3U8文件

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  

转换成YUV原始文件

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

JavaScript函数式编程术语清单

函数式编程术语

目录

  • Arity
  • 高阶组件 (HOF)
  • 偏函数应用 (Partial Application)
  • 柯里化 (Currying)
  • 自动柯里化 (Auto Currying)
  • 函数组合 (Function Composition)
  • Continuation
  • 纯函数 (Purity)
  • 副作用 (Side effects)
  • 幂等性 (Idempotent)
  • Point-Free 风格 (Point-Free Style)
  • 谓词 (Predicate)
  • 契约 (Contracts)
  • Guarded Functions
  • 范畴 (Category)
  • 值 (Value)
  • 常量 (Constant)
  • 函子 (Functor)
    • 一致性 (Preserves identity)
    • 组合性 (Composable)
  • Pointed Functor
  • Lift
  • 引用透明性 (Referential Transparency)
  • Equational Reasoning
  • 匿名函数 (Lambda)
  • Lambda Calculus
  • 惰性求值 (Lazy evaluation)
  • 独异点 (Monoid)
  • Monad
  • Comonad
  • Applicative Functor
  • 态射 (Morphism)
    • 自同态 (Endomorphism)
    • 同构 (Isomorphism)
  • Setoid
  • 半群 (Semigroup)
  • Foldable
  • Traversable
  • 类型签名 (Type Signatures)
  • Union type
  • Product type
  • Option

CentOS7中防火墙的一些常用配置介绍

常用命令

# 启动 
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

参考

https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux/7/html/security_guide/sec-using_firewalls

C语言运算符优先级巧记

运算符优先级表

优先级 运算符 名称或含义 使用形式 结合方向 说明
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个: ,

总结

同一优先级的运算符,运算次序由结合方向所决定。

简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

vim速记手册

vim
vim分为四种模式:

概述

模式
  • 普通模式(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 开启/关闭代码高亮 -

替换命令

  1. :s/target/replacement/:替换当前行的第一个targetreplacement

    :s/target/replacement/g:替换当前行的所有的targetreplacement

  2. :n,$s/target/replacement/:替换第n到最后一行的第一个targetreplacement

    :n,$s/target/replacement/g:替换第n到最后一行的所有的targetreplacement

  3. :%s/target/replacement:替换所有行的第一个targetreplacement

    :%s/target/replacement/g:替换所有行的所有的targetreplacement

  4. #+作为分隔符,/作为匹配项中的内容:

    :s#target/#/replacement#g:替换所有行的第一个target//replacement

    :%s+/oradata/apras/+/user01/apras1+g:替换所有行的/oradata/apras//user01/apras1/

js常见排序算法

十大经典算法

排序算法是《数据结构与算法》中最基本的算法之一。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:

sort

关于时间复杂度:

  1. 平方阶 (O(n^2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
  2. 线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
  3. O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
  4. 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性:

  • 稳定的排序算法:冒泡排序、插入排序、并归排序和基数排序。
  • 不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

名词解释

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;
}

通过Prosemirror了解富文本编辑器

了解富文本编辑器基础

浏览器特性

  • contenteditable
  • document.execCommand 兼容性、安全性、可定制性差等原因被废弃

contentEditable之坑

  • 厂商实现差异
  • 不可预测的表现
  • 行内标签嵌套

视觉上等价DOM结构上不等价、生成DOM不总是符合预期

详细介绍:ContentEditable困境与破局

技术类型:

  • L0:早期的轻量级编辑器
    • 基于 contenteditable
    • 使⽤ document.execCommand
    • 代码量:⼏千—⼏万⾏代码
  • L1 :CKEditor、TinyMCE、Draft.js、Slate、prosemirror、石墨文档、腾讯文档
    • 基于 contenteditable
    • 不⽤ document.execCommand,⾃主实现
    • 架构大部分采用MVC模式、通过数据驱动视图与时俱进
    • 代码量:⼏万—⼏⼗万⾏
  • L2 :google docs、office word online 、 iCloud pages、wps在线版
    • 不⽤ contenteditable,⾃主实现
    • 不⽤ document.execCommand,⾃主实现
    • 自主实现光标和选区
    • 代码量:⼏⼗万⾏—⼏百万⾏

不同类型的优劣

类型 优势 劣势
L0 技术⻔槛低,短时间内快速研发 可定制的空间⾮常有限
L1 站在浏览器肩膀上,能够满⾜ 99% 业务场景 ⽆法突破浏览器本身的排版效
L2 技术都掌控在⾃⼰⼿中,⽀持个性化排版 技术难度相当于⾃研浏览器、数据库

Prosemirror

简介

ProseMirror是一个用于实现富文本编辑器的开源框架。它提供了一个可定制、可扩展的编辑器,能够适应多种编辑场景,如在线文本编辑、协作编辑、内容管理系统等。作者 Marijncodemirror 编辑器和 acorn 解释器的作者,前者已经在 ChromeFirefox 自带的调试工具里使用了,后者则是 babel 的依赖,他还撰写了一本广受欢迎的 JavaScript 编程入门书籍《Eloquent JavaScript》

实现原理

ProseMirror依赖contentEditable,不过非常厉害的是ProseMirror将主流的前端的架构理念应用到了编辑器的开发中,比如彻底使用纯JSON数据描述富文本内容,引入不可变数据以及Virtual DOM的概念,还有插件机制、分层、Schemas(范式)等等。

特点:

  • 模块化设计
  • 实时协作编辑
  • 拼写检查和样式格式化
  • 内置功能丰富(撤销/重做、拖拽/复制/粘贴、快捷键等)
  • 注重性能(高效更新)

核心模块

  • **prosemirror-model 模型层:**定义编辑器的文档模型,用来描述编辑器内容的数据结构
  • prosemirror-state 状态层描述编辑器整体状态,包括文档数据、选区等
  • prosemirror-view 视图层: 用于将编辑器状态展现为可编辑的元素,处理用户交互
  • prosemirror-transform 事务层: 通过事务的方式修改文档,并支持修改记录、回放、重排等

其他模块

  • prosemirror-commands 基本编辑命令
  • prosemirror-keymap 键绑定
  • prosemirror-history 历史记录
  • prosemirror-inputrules 输入宏
  • prosemirror-collab 协作编辑
  • prosemirror-schema-basic 简单文档模式

使用

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。

骨架schema

每个 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})
  • 内容表达式 content expressions
  • 标记 Marks
  • 序列化与解析 Serialization and Parsing node spec 中指定 toDOMparseDOM实现解析

状态层state

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

Transform系统是 Prosemirror 的核心工作方式. 它是 transactions的基础, 其使得编辑历史跟踪和协同编辑成为可能。

why?

  1. 配合 Immutable 数据结构 可以使代码的保持清晰
  2. transform系统可以保留document更新的痕迹,便于实现undo history这种历史记录
  3. 为了实现协同编辑

Steps: 原子操作

一个编辑行为可能会产生一个或者多个 steps。例如: ReplaceStepAddMarkStep

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。支持链式调用

常见的方法 deleteingreplaceing, addingremoveing 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

视图层view

Prosemirror 的 editor view 是一个用户界面的 component, 它展示 editor state给用户, 同时允许用户对其执行编辑操作。

Editable DOM

  • 基于浏览器contenteditable
  • 保证DOM Selection 和editor state的selection一致性
  • 大部分事件比如光标移动、鼠标事件、输入事件都交给浏览器处理,浏览器处理完后,Prosemirror检测当前DOM或者selection的变化,然后把这些变化部分转化为transaction

数据流

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 方面的一些能力

  • Node decorations增加样式或者其他 DOM 属性到单个 node 的 DOM 上去.
  • Widget decorations 在给定位置插入一个 DOM node, 其不是实际文档的一部分
  • Inline decorations在给定的 range 中的行内元素增加样式或者属性, 和 node decoration 类似, 不过只针对行内元素.
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 }
}

Commands

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

基于 ProseMirror的无头编辑器. Tiptap提供了合理的默认值、通过抽象Extension扩展的提供统一的写法,增加了更多commonds便于实际使用,整体提高了编辑器的可用性和扩展性,可以作为开箱即用的工程化方案。

remirror

基于[ProseMirror](https://github.com/ProseMirror/prosemirror)的用于构建*跨平台*文本编辑器的 React工具包

[BlockNote

BlockNote 是一个Notion风格的基于ProseMirrorTiptap构建的可扩展块的文本编辑器

参考

https://prosemirror.net/docs/guide/
https://tiptap.dev/introduction
开源富文本编辑器技术的演进(2020 1024)
https://www.wenxi.tech/principles-of-modern-editor-technology

推荐

https://www.notion.so/

JavaScript中call,apply,bind以及this的理解

说明

在JavaScript中调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义形参,每个函数接收两个附加的参数:thisarguments。参数this在面向对象中非常重要,它取决于调用的模式。在JavaScript**有四种**调用模式:**方法调用模式、函数调用模式、构造器调用模式、和apply(),call()方法调用模式。这些模式在如何初始化关键参数this存在差异。本文首先要提到的是this,抛开this单独去说这些方法是没有意义的。然后是如何妙用call,apply,bind这些方法去改变this的指向。

目录

  • this在不同模式下的意义
  • 借鸡下蛋之妙用call,apply
  • 深入理解bind函数

this在不同模式下的意义

  1. 全局上下文
    在全局运行上下文中(在任何函数体外部),this 指代全局对象,无论是否在严格模式下。例如在浏览器环境中任何定义在全局的属性,方法都将成为全局对象window的属性和方法。
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
  1. 函数上下文
    在函数内部,this的值取决于函数是如何调用的。
//直接调用
function f1(){
  return this;
}
f1() === window; // true
//this的值不是由函数调用设定。因为代码不是在严格模式下执行,this 的值总是一个对象且默认为全局对象
function f2(){
  "use strict"; // 这里是严格模式
  return this;
}
f2() === undefined; // true
//在严格模式下,this 是在进入运行环境时设置的。若没有定义,this的值将维持undefined状态。也可能设置成任意值。
  1. 对象方法中的this
    当以对象里的方法的方式调用函数时,它们的 this 是调用该函数的对象.
    下面的例子中,当 o.f() 被调用时,函数内的this将绑定到o对象。
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
  1. 原型链中的this
    相同的概念在定义在原型链中的方法也是一致的。如果该方法存在于一个对象的原型链上,那么this指向的是调用这个方法的对象,表现得好像是这个方法就存在于这个对象上一样。
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
  1. 构造函数中的 this
    当一个函数被作为一个构造函数来使用(使用new关键字),它的this与即将被创建的新对象绑定。
    注意:当构造器返回的默认值是一个this引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this。
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
  1. DOM事件处理函数中的 this
    当函数被用作事件处理函数时,它的this指向触发事件的元素(一些浏览器在动态添加监听器时不遵守这个约定,除非使用addEventListener 这句不太确定翻译的是否正确)。
// 被调用时,将关联的元素变成蓝色
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);
}

借鸡下蛋之妙用call,apply

apply

fun.apply(thisArg[, argsArray])方法在指定 this 值和参数(参数以数组或类数组对象的形式存在)的情况下调用某个函数。

  • thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
  • argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数

在调用一个存在的函数时,你可以为其指定一个 this 对象。 this指当前对象,也就是正在调用这个函数的对象。使用apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量.你也可以使用 arguments 对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。 你可以使用arguments来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。

  1. 使用apply来链接构造器
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();
};
  1. 使用apply和内置函数
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]);
  1. 在"monkey-patching"中使用apply
var originalfoo = someobject.foo;
someobject.foo = function() {
  //在调用函数前干些什么
  console.log(arguments);
  //像正常调用这个函数一样来进行调用:
  originalfoo.apply(this,arguments);
  //在这里做一些调用之后的事情。
}
call

fun.call(thisArg[, arg1[, arg2[, ...]]]) 使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.该方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。通过 call 方法,你可以在一个对象上借用另一个对象上的方法,比如Object.prototype.toString.call([]),就是一个Array对象借用了Object对象上的方法。

  1. 使用call方法调用父构造函数
    在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于Java中的写法。下例中,使用 Food 和 Toy 构造函数创建的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。
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);
  1. 使用call方法调用匿名函数
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);
}
  1. 使用call方法调用匿名函数并且指定上下文的'this'
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]
 

bind函数

fun.bind(thisArg[, arg1[, arg2[, ...]]])bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

  1. 创建绑定函数
    bind() 最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的 this 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 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
  1. 偏函数(Partial Functions)
    bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind()的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。
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]
  1. 配合 setTimeout
    在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window (或全局)对象。当使用类的方法时,需要 this 引用类的实例,你可能需要显式地把 this 绑定到回调函数以便继续使用实例。
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'方法
  1. 快捷调用
    在你想要为一个需要特定的 this 值得函数创建一个捷径(shortcut)的时候,bind() 方法也很好用.你可以用 Array.prototype.slice 来将一个类似于数组的对象(array-like object)转换成一个真正的数组,就拿它来举例子吧。你可以创建这样一个捷径:
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读者可以自行去研究;

JavaScript闭包以及常见用法

说明

学过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.

翻译成汉语其实就是闭包可以获取,操作内部作用域中定义的变量。
看完这段英语相信你已经对闭包有一定的感性理解。那么我们来从语法的角度分析一下闭包的组成。
它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。一说环境可能有点高深,我用数学表达来表述就是:闭包=函数(可匿名)+环境(作用域+局部变量+入参) 这下是不是很清晰了。

闭包的常见应用

  1. 模拟私有变量 (Private variable)

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
 }();

Linux进程命令篇

lsof 列出当前系统打开文件

查看指定端口号语法格式: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 

netstat

用于显示 tcp,udp 的端口和进程等相关情况:netstat -tunlp
netstat 查看端口占用语法格式:netstat -tunlp | grep 端口号

  • -t (tcp) 仅显示tcp相关选项
  • -u (udp)仅显示udp相关选项
  • -n 拒绝显示别名,能显示数字的全部转化为数字
  • -l 仅列出在Listen(监听)的服务状态
  • -p 显示建立相关链接的程序名
[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 显示当前进程状态

显示本用户的进程: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

终止进程:kill -9 [PID]

如何使用JavaScript检测浏览器是否支持flash

image

背景

在视频网站经常见到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!

chrome 66自动播放策略改变

ba44d518-eb46-4ce8-8a65-6abae68a8840

背景

Web浏览器正在朝着更严格的自动播放策略发展,以便改善用户体验,最大限度地降低安装广告拦截器的积极性并减少昂贵和/或受限网络上的数据消耗。这些更改旨在为用户提供更大的播放控制权,并使开发商获得合法用例。

新的特性

Chrome的自动播放政策很简单:

  • 静音自动播放总是允许的。
  • 在下列情况下允许使用声音自动播放:
    • 用户已经与域进行了交互(点击,tap等)。
    • 在桌面上,用户的媒体参与指数阈值(MEI)已被越过,这意味着用户以前播放带有声音的视频。
    • 在移动设备上,用户已将该网站添加到主屏幕。
    • 顶部框架可以将自动播放权限授予其iframe以允许自动播放声音。

媒体参与指数(Media Engagement Index)(MEI)

MEI衡量个人在网站上消费媒体的倾向。Chrome 目前的方法是访问每个来源的重要媒体播放事件的比率:

  • 媒体消耗(音频/视频)必须大于7秒。
  • 音频必须存在并取消静音。
  • 视频选项卡处于活动状态。
  • 视频大小(以像素为单位)必须大于200x140。

因此,Chrome会计算媒体参与度分数,该分数在定期播放媒体的网站上最高。足够高时,媒体播放只允许在桌面上自动播放。MEI是谷歌自动播放策略的一部分。它是一个算法,参考了媒体内容的持续时间、浏览器标签页是否活动、活动标签页视频的大小这一系列元素。不过也正因此,开发者难以在所有的网页上都测试这一算法的效果。

用户的MEI位于chrome://media-engagement/内部页面
media-engagement

开发者开关

作为开发者,您可能需要在本地更改Chrome浏览器自动播放政策行为,以根据用户的参与情况测试您的网站。

  • 您可以决定通过将Chrome标志“自动播放策略”设置为“无需用户手势”来完全禁用自动播放策略 chrome://flags/#autoplay-policy。这样您就可以测试您的网站,就好像用户与您的网站保持紧密联系一样,并且始终允许播放自动播放。
  • 您也可以决定禁止使用MEI以及默认情况下全新MEI获得播放自动播放的网站是否允许新用户使用,从而决定禁止播放自动播放。这可以用两个来完成 内部开关用chrome.exe --disable-features=PreloadMediaEngagementData,AutoplayIgnoreWebAudio, MediaEngagementBypassAutoplayPolicies

Iframe 委托授权

一个功能政策使开发人员可以选择性地启用和禁用的各种浏览器的功能和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企业政策

Chrome企业策略可以改变这种新的自动播放行为,以用于例如信息亭或无人值守系统。查看 配置策略和设置帮助页面,了解如何设置这些新的与自动播放相关的企业策略:

  • 该“AutoplayAllowed”策略控制自动播放是否允许。
  • 该“AutoplayWhitelist”政策,允许您指定的URL模式的白名单,其中自动播放将始终启用。

开发人员最佳实践

视频元素

  • 永远不要假设视频会播放,并且在视频不是真正播放时不要显示暂停按钮。
  • 关注播放函数返回的Promise。
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对象,因为一切都发生在这个环境之中。

AudioContext播放声音
  1. 先请求音频文件,放到ArrayBuffer里面,然后用AudioContext的API进行decode解码,解码完了再让它去play。
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();
    });
}
  1. 实例化AudioContext
// Safari是使用webkit前缀
let context = new (window.AudioContext || window.webkitAudioContext)();
  1. 解码播放
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));

关于音频播放的可以参阅这篇文章讲的比较详细,这里不再讨论。

AudioContext创建时机
  • 页面加载时创建
    那么resume()在用户与页面进行交互之后(例如,用户单击按钮),您必须在某个时间进行调用。
// 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、blob互转

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地址

articleocw-57dfeb2a430a6
JavaScript是无法获得或存储在客户端的IP。但是由于JavaScript能够发送HTTP请求,而服务器端语言能够获取用户的公网IP,所以你可以利用这个获取IP。 换句话说,如果你想得到一个用户就取决于请求任何服务器检索公网IP。 随着WebRTC技术的发展,利用rtcpeerconnection可以检索用户私有IP。

使用 webRTC (获取私有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);
});

使用第三方服务(获取公网IP)

  • 不安全的http链接
$.getJSON('http://ipinfo.io', function(data){
    console.log(data);
});
  • 安全的https链接(推荐)
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状态码查询表

image

HTTP状态码分类

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:

分类 分类描述
1xx 信息性,服务器收到请求,需要请求者继续执行操作
2xx 成功,操作被成功接收并处理
3xx 重定向,需要进一步的操作以完成请求
4xx 客户端错误,请求包含语法错误或无法完成请求
5xx 服务器错误,服务器在处理请求的过程中发生了错误

HTTP状态码列表

状态码 原因短语 含义
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协议的版本,无法完成处理

TBD主干开发介绍

主干开发是一套代码分支管理策略,开发人员之间通过约定向被指定为主干的分支提交代码,以此抵抗因为长期存在的多分支导致的开发压力。 此举可避免分支合并的困扰,保证随时拥有可发布的版本。
image
在介绍主干开发前先来对比一下目前的分支管理策略。

Git Flow

简介

Git分支配置的规则,也是实现该规则的工具 最完整的体系。

特点

  1. 两个长期分支:master和develop
  2. 三个短期分支:feature(功能)、hotfix(补丁)、release(预发)
  3. master分支用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的发布版;develop用于日常开发,存放最新的开发版。
  4. 短期存在的分支一旦完成开发,它们就会被合并进develop或master,然后被删除。
    长期分支:
    master(主分支):保存最新已发布版本基线的分支。
    develop 分支(开发分支):对开发的功能进行集成的分支。
    短期分支:
    feature 分支(功能分支):开发者进行功能开发的分支。从Develop分支上面分出来的。开发完成后,要再并入Develop。
    hotfix 分支(补丁分支):对线上缺陷进行修改工作的分支。从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。
    release 分支(预发分支):负责版本发布的分支。从Develop分支上面分出来的,预发布结束以后,必须合并进Develop和Master分支。

优势

  1. 提供了相对完备的各种分支,覆盖软件开发过程中的大部分场景。
  2. 各个feature之间代码隔离,可以独立开发、测试,分支清晰。

劣势

  1. 分支较多时,合并代码时容易存在冲突。
  2. 相对复杂,学习成本相对来说会高一些。

应用场景

  1. 对产品质量上要求高,发布的版本有较长的维护周期。
  2. 需要支持多个版本并且相互独立,同时后续各版本开发。
  3. 大规模团队适用。

图解

image

GitHub Flow

简介

Git Flow 对于大部分开发人员和团队来说,稍微有些复杂,而且没有 GUI 图形页面,只能命令行操作,所以为了更好的解决这些问题,GitHub Flow 应运而生了。

特点

  1. 只有一个长期分支master以及各种短期的feature分支。
  2. 只要在master分支中的代码一定是可以部署的。
  3. 如果想获得反馈或建议,或者愿意合并分支,需要发起一个pull Request。
  4. 当 review 或者讨论通过后,代码会合并到目标分支。
  5. 一旦合并到 master 分支,应该立即发布。

优势

  1. 简单好理解。
  2. 对于"持续发布"的产品,可以说是最合适的流程,简化部署,持续交付。

劣势

  1. master合并后如果不发布,会造成线上和master不一致。
  2. 只有一个主分支,不能部署不同的环境(例如:测试环境,预发环境,正式环境)。

应用场景

  1. 持续发布,快速迭代,每次更新,直接部署。
  2. 适合中小型项目,开发人员少的项目。

图解

image

GitLab Flow

简介

结合了Git Flow和GitHub Flow的优点,折中的版本。

特点

  1. 拥有三个长期分支master、pre-production、production,其他分支为临时分支。
  2. master 分支反映的是部署在集成环境上的代码,pre-production 分支反映的是部署在预发环境的代码,production 分支反映的最新部署在生产环境的代码。
  3. 生产环境中出现问题,要先再master分支解决问题,然后提交到pre-production分支验证,最后在确定没有问题的前提下,更新到production分支。

优势

清晰可控,由于修正是在master层面,所以确保所有的提交都是测试环境中通过测试的。

劣势

  1. 相对于github flow来说,gitlab flow 更复杂。
  2. 当维护较多版本时,会变得像git flow似的比较复杂。

应用场景

需要进行预发布/周期性版本发布的项目。

图解

image

TBD

简介:

全名Trunk-based development、略称TBD.基于主干开发,所有开发人员都只能在一个开发分支中开发。

特点:

  1. 有且仅有一个开发分支,即主干分支。所有改动都发生在主干分支。
  2. 发布直接从主干拉发布分支,有许多个发布分支。
  3. 主干上进行的修复需要根据缺陷的修复策略,确定是否 cherry pick 到对应版本的发布分支。git cherry-pick命令的作用,就是将指定的提交(commit)应用于其他分支。

优势:

  • 避免分支合并的困扰,保证随时拥有可发布的版本
  • 主干开发是助力实现 持续集成 和 持续交付 的关键因素。开发团队的成员一天多次地将代码提交到主干分支,满足了持续交付的必要条件。团队的工作在 24 小时内就可以被整合,这保证了代码版本随时处于可发布状态,使得持续交付成为可能。
  • 你可以选择直接向主干分支提交代码的方式(适用于小团队)或者采用 MR 的方式,merge approve后可以选择删除特性分支
  • 根据团队规模和提交频率, Feature分支可用于合并到主干分支前的CR和持续集成

劣势:

主干分支是所有开发人员公用的,一个开发人员引入的 bug 可能对其他很多人造成影响。

应用场景:

协作能力强的小规模团队。

图解:

image

image

image

CI/CD简介

image

自动打版本工具

standard-version

参考资料

https://cloud.google.com/solutions/devops/devops-tech-trunk-based-development?hl=zh-cn
https://cn.trunkbaseddevelopment.com/

JavaScript函数的特性系列目录

Function

javascript对函数的实现是最好的,函数可以说是javascript的灵魂,它几乎可以说是无所不能的,函数包含一组语句,他们是JavaScript的基础模块单元,用与代码复用、信息隐藏和组合调用。函数用与指定对象行为。

所谓编程就是将一组需求分解成函数与数据结构的技能

程序设计=数据结构+算法

  • 函数对象(Function Object)
  • 函数字面量(Function Literal)
  • 调用(Invocation)
  • 参数(Arguments)
  • 返回(Return)
  • 异常(Exception)
  • 给类型增加方法(Augmenting Type)
  • 递归(Recursion)
  • 作用域(Scope)
  • 闭包(Closure)
  • 回调(Callback)
  • 模块(Module)
  • 级联(Cascade)
  • 套用
  • 柯里化(Curry)
  • 记忆(Memoization)

本文只是简单列举出函数的重要特性,由于每种特性不是三两两语就能说清楚,干脆不在一篇文章中介绍。后续会一一介绍。文章列举属性来自JavaScript语言精粹。

Markdown简明语法手册

说明

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常用配置详解

nginx.conf

main全局模块

#定义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模块

#工作模式与连接数上限
events  {
#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ];epoll模型是Linux 2.6以上
#版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll; 
#单个进程最大连接数(最大连接数=连接数*进程数)
worker_connections 65535;
}

HTTP模块

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连接数的时候需要使用

upstream指令

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模块

#虚拟主机的配置
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

#对 "/" 启用反向代理
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; 分割配置文件,方便管理
}

HTTPS server

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;  
    }  
}  

mac 解决下brew安装python3的一系列权限问题

Step 1.

$ brew install python3

error1

Step2.

$ cd /usr/local
$ sudo chown -R $(whoami) $(brew --prefix)/*

try again 😡

$ brew install python3

3578fb1b-9205-4f50-b0fa-fed094be6d7c

Step3.

$ brew unlink python && brew link python

error3

Step4. 😠

$ brew link --overwrite python3
$ sudo brew link --overwrite python3

error4

error5

Step5. 😡

$ sudo mkdir /usr/local/Frameworks
$ sudo chown $(whoami):admin /usr/local/Frameworks
$ brew link python

error6

成功!😄

视频H5 video最佳实践

cover_900x500
随着 4G 的普遍以及 WiFi 的广泛使用,手机上的网速已经足够稳定和高速,以视频为主的 HTML5 也越来越普遍了,相比帧动画,视频的表现更加丰富,这里介绍一些实践经验。

video的属性

<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>
  • src: 视频的地址
  • controls: 加上这个属性,Gecko 会提供用户控制,允许用户控制视频的播放,包括音量,跨帧,暂停/恢复播放。
  • poster: 属性规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。如果未设置该属性,则使用视频的第一帧来代替。
  • preload: 属性规定在页面加载后载入视频。
  • webkit-playsinline和playsinline: 视频播放时局域播放,不脱离文档流 。但是这个属性比较特别, 需要嵌入网页的APP比如WeChat中UIwebview 的allowsInlineMediaPlayback = YES webview.allowsInlineMediaPlayback = YES,才能生效。换句话说,如果APP不设置,你页面中加了这标签也无效,这也就是为什么安卓手机WeChat 播放视频总是全屏,因为APP不支持playsinline,而ISO的WeChat却支持。
    这里就要补充下,如果是想做全屏直播或者全屏H5体验的用户,IOS需要设置删除 webkit-playsinline 标签,因为你设置 false 是不支持的 ,安卓则不需要,因为默认全屏。但这时候全屏是有播放控件的,无论你有没有设置control。 做直播的可能用得着播放控件,但是全屏H5是不需要的,那么去除全屏播放时候的控件,需要以下设置:同层播放
  • x-webkit-airplay="allow" : 这个属性应该是使此视频支持ios的AirPlay功能。使用AirPlay可以直接从使用iOS的设备上的不同位置播放视频、音乐还有照片文件,也就是说通过AirPlay功能可以实现影音文件的无线播放,当然前提是播放的终端设备也要支持相应的功能
  • x5-video-player-type: 启用同层H5播放器,就是在视频全屏的时候,div可以呈现在视频层上,也是WeChat安卓版特有的属性。同层播放别名也叫做沉浸式播放,播放的时候看似全屏,但是已经除去了control和微信的导航栏,只留下"X"和"<"两键。目前的同层播放器只在Android(包括微信)上生效,暂时不支持iOS。至于为什么同层播放只对安卓开放,是因为安卓不能像ISO一样局域播放,默认的全屏会使得一些界面操作被阻拦,如果是全屏H5还好,但是做直播的话,诸如弹幕那样的功能就无法实现了,所以这时候同层播放的概念就解决了这个问题。不过在测试的过程中发现,不同版本的IOS和安卓效果略有不同
  • x5-video-orientation: 声明播放器支持的方向,可选值landscape 横屏, portraint竖屏。默认值portraint。无论是直播还是全屏H5一般都是竖屏播放,但是这个属性需要x5-video-player-type开启H5模式
  • x5­-video­-player­-fullscreen:全屏设置。它又两个属性值,ture和false,true支持全屏播放,false不支持全屏播放。其实,IOS 微信浏览器是Chrome的内核,相关的属性都支持,也是为什么X5同层播放不支持的原因。安卓微信浏览器是X5内核,一些属性标签比如playsinline就不支持,所以始终全屏。

全屏处理

  • ios
    ios加playsinline属性,之前只带webkit前缀的在ios10以后,会吊起系统自带播放器,两个属性都加上基本ios端都可以保证内敛到浏览器webview里面了。如果仍有个别版本的ios会吊起播放器,还可以引用一个库iphone-inline-video(具体用法很简单看它github,这里不介绍了,只需加js一句话,css加点),github地址加上playsinline webkit-playsinline这两个属性和这个库基本可以保证ios端没有问题了(不过亲测,只加这两个属性不引入库好像也是ok的,至今没有在ios端微信没有出现问题,如果你要兼容uc或者qq的浏览器建议带上这个库).
  • android
    x5-video-player-type="h5"属性,腾讯x5内核系的android微信和手Q内置浏览器用的浏览器webview的内核,使用这个属性在微信中视频会有不同的表现,会呈现全屏状态,貌似播放控件剥去了,至少加了这个属性后视频上层可以有其他dom元素出现了(非腾讯白名单机制的一种处理措施)。
<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下监听canplaycanplaythrough(是否已缓冲了足够的数据可以流畅播放),当加载时是不会触发的,即使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
微信端视频播放问题

如何使用JavaScript下载pdf等文件

articleocw-59983b4f358aa

原理

使用window.URL.createObjectURLwindow.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();

深入理解Event Loop

浏览器环境

基本概念

为了协调事件、用户交互、脚本、呈现、网络等,UA必须使用事件循环的机制。事件循环有两种模式:用于browsing contexts(浏览环境上下文)的循环,以及用于workers的循环。

  • browsing contexts事件循环
    每个browsing contexts event loop个至少含有一个browsing context上下文环境,该事件循环依赖与环境,环境消失的话该事件机制也将销亡.
  • workers 事件循环
    workers与此类似 每个worker 有一个事件循环,并通过worker processing model 管理事件循环的生命周期.

task queue(任务队列)

一个事件循环会有一个或者多个任务队列。任务队列是一个有序的list集合,用来处理下面的任务:

  • Events 任务的分发
  • Parsing 解析处理,例如HTML parser。
  • Callbacks 处理回调任务
  • Using a resource 异步获取一个资源
  • DOM manipulation 操作DOM

task(任务)

  • 任务被定义是来自指定的任务源,来自同一个指定任务源的任务总是会被注入到指定事件循环的同一个任务队列,但是来自不同任务源的任务是可能被放到不同的队列,也可能放到同一队列。
  • 每个事件循环有一个当前运行时任务,初始化时候为空,被用来处理被注入的事件。每个事件循环同时还有个microtask的flag用来检测微任务默认情况为false。来用防止注入事件时调用microtask检测算法。

Processing model(事件循环处理器)

一个事件循环其实就是在不断运行下面这些步骤的操作:
1. 把oldestTask标记为oldest task. UA可以选择任意任务队列,如果没有选择跳到Microtasks微任务处理
2. 将当前运行任务设置成 oldestTask.
3. 运行 oldestTask.
4. 将事件循环的当前运行任务 置为null
5. 从任务队列中移除oldestTask.
6. Microtasks: 微任务检测点,执行微任务检测.
7. 更新渲染(Update rendering) 主要是浏览器渲染过程不详细展开.
8. worker事件循环判断.

微任务检测

  1. 将微任务检测点flag设置为true
  2. 当事件循环的微任务队列不为空,执行下面操作与上述操作类似:
    1. oldestMicrotask ->oldest microtask.
    2. 当前运行任务设置成 oldestMicrotask.
    3. 运行 oldestMicrotask.
    4. 将事件循环当前运行任务 置为null
    5. 从微任务队列中移除oldestMicrotask.
  3. 每次与事件循环相关的环境对象设置.
  4. 清理索引数据库事务
  5. 将微任务检测点flag设置为false

事件循环

上述概念为whatwg规范概念,具体实现还得看浏览器厂商。之所以称为事件循环,是因为它经常被用于类似如下的方式来实现:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

由于js的运行环境是单线程的,函数调用会形成了一个栈帧,对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域 ,除此之外JavaScript 运行时还包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈拥有足够内存时,从队列中取出一个消息进行处理。
看图来分析如下:
js_runtime
主线程运行时函数会被压入函数调用栈等待执行,当调用栈中的函数被调用时,任务分发器会根据任务源把对应任务放入不同的event队列中。如webaips产生的回调会被放入回调队列,微任务microtask会被放入微任务队列。事件循环处理器也就是event loop按照前面的步骤做loop。

注意

  • 一个进程中,事件循环是唯一的,但是任务队列可以拥有多个。
  • 每个任务会在任务结束后进行微任务检查并处理微任务。
  • 任务队列又分为macrotask(宏任务)与microtask(微任务),在最新标准中,它们被分别称为task与jobs.
  • 常见macrotask和microtask
    • macrotask ->script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering.
    • microtask -> process.nextTick, Promise, Object.observe, MutationObserver
  • setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
  • 同种任务顺序优先级
    • macrotask:script(整体代码)->setTimeout(setInterval同源)->setImmediate
    • microtask:process.nextTick->Promise(then)
  • 宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面
  • 所有微任务也按顺序执行,且在以下场景会立即执行所有微任务
    • 每个回调之后且js执行栈中为空。
    • 每个宏任务结束后。

Node.js 环境

概念

当Node.js启动时会初始化event loop,下图是一个简化版的事件循环执行顺序,大体分为六个阶段。

   ┌───────────────────────┐
┌─>        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐incoming:   
           poll          <─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘
  • timers 阶段: 这个阶段执行setTimeoutsetInterval的回调.
  • I/O callbacks 阶段: 执行除了被close或者由定时器,setImmediate所产生的回调外的所有回调.
  • idle, prepare 阶段: 内部使用.
  • poll 阶段: 检索新的IO事件,node有时会阻塞这里.
  • check 阶段: 调用setImmediate的回调.
  • close callbacks 阶段: 一些关闭回调的操作,例如,socket.on('close', ...).

详细

timer 定时器

对于定时器和浏览器API类似,需要注意的是定时器何时执行是由poll 阶段决定的,所有精确度不够有时可能被延迟。

poll

poll阶段主要做两件事:

  • 执行到达timer时间的脚本.
  • 处理poll队列中的事件.

如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll队列不为空,event loop将同步的执行队列里的callback,直至队列为空,或执行的callback到达系统内存限制
  • 如果poll 队列为空,将会发生下面情况:
    • 如果代码已经被setImmediate设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的事件.
    • 如果代码没有设定setImmediate,event loop将阻塞在该阶段等待callbacks加入poll队列.

一旦poll队列为空,事件循环将检查timer是否达到时间,如果有一个或者多个timer到达时间事件循环就会返回到timers阶段执行timers的回调.

check

这个阶段容许立即执行一个回调在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? 加密媒体扩展介绍

原文链接: 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应用程序:当用户登录应用程序,应用程序决定了用户的身份和特权。

EME如何工作?

eme
EME组件如何交互的,对应下面的代码示例:

如果多种格式和编解码器都是支持的,MediaSource.isTypeSupported(),或HTMLMediaElement.canPlayType()都可以用来选择正确的一个。然而,CDM可能只支持浏览器支持加密的内容的一个子集。最好是在选择一个格式和编解码器之前,使用一个MediaKeys配置。如果应用程序等待加密事件后,然而 MediaKeys显示它无法处理所选的格式/编解码器,它可能是来不及切换而不得不中断播放。

推荐的流程是先通过MediaKeys,然后使用MediaKeysSystemAccess.getConfiguration() 获取可靠的配置。

如果只有一个格式/编码器去选择,然后就没有必要
getConfiguration(). 然而,最好是先建立MediaKeys。等待加密事件的唯一理由是如果没有办法知道内容是否加密,但实际上这是不可能的。

  1. 一个web应用程序试图播放有一个或多个加密流音频或视频。
  2. 浏览器认出媒体是加密的(见下面如何发生),然后会通过从媒体获得的加密元数据即(initData)触发一个加密的事件。
  3. 应用程序处理加密事件:
    • 如果没有MediaKeys对象与媒体元素关联,首先选择一个可用的密钥系统通过使用navigator.requestMediaKeySystemAccess()检查哪些系统可用,然后通过MediaKeySystemAccess为密钥系统创建为一个可用的MediaKeys对象。注意,MediaKeys对象的初始化应该在第一个加密事件之前。通过选择一个可用的密钥系统,获得许可证服务器的URL是一个独立应用程序。MediaKeys对象代表了所有可用的密钥来解密音频或视频的媒体元素。它代表了CDM实例并提供访问CDM,专门用于创建密钥会话,用于获取密钥从许可证书服务器。
    • 一旦MediaKeys对象被创建,将其分配给媒体元素:setMediaKeys()分配给HTMLMediaElement元素,所以可以使用播放期间使用密钥,例如解码的时候。
  4. 应用程序建立MediaKeySession通过调用MediaKeys上的createSession()方法。MediaKeySession代表了证书和它密钥的生命周期。
  5. 应用程序通过将加密处理中获取的媒体数据传递给CDMl来生成许可证请求。通过MediaKeySession调用generateRequest()方法。
  6. CDM出发一个消息事件:从证书服务器获取密钥的请求。
  7. MediaKeySession 对象接收到消息事件然后应用程序通过(例如xhr)发送消息到证书服务器。
  8. 应用程序接收到响应从证书服务器并且传递数据到CDM使用MediaKeySessionupdate()方法。
  9. CDM解密媒体使用证书中的密钥。一个有效的密钥可能被使用在MediaKeys关联的媒体元素任何会话中。CDM会访问有密钥id索引的密钥和策略。
  10. 媒体播放恢复。

浏览器如何知道媒体是加密的?

此信息位于媒体容器文件的元数据中,该文件将采用ISO BMFF或WebM等格式。对于ISO BMFF,这意味着标题元数据,称为保护方案信息框。WebM使用Matroska ContentEncryption元素以及一些WebM特定的添加项。在EME特定的注册表中为每个容器提供准则。

请注意,CDM和许可证服务器之间可能存在多个消息,并且此过程中的所有通信对浏览器和应用程序都是不透明的:消息只能由CDM和许可证服务器理解,但应用程序层可以看到什么类型的消息CDM正在发送。许可证请求包含CDM有效性(和信任关系)以及在生成的许可证中加密内容密钥时使用的密钥。

...但CDM实际上做了什么?

EME实现本身并不提供解密媒体的方式:它只是为Web应用提供API来与内容解密模块进行交互。

CDM实际做的不是由EME规范定义的,CDM可以处理媒体的解码(解压缩)以及解密。至少从最强大的角度来看,CDM功能有几种可能的选择:

  • 仅解密,使用普通媒体管道进行播放,例如通过<video>元素。
  • 解密和解码,将视频帧传递给浏览器进行渲染。
  • 解密和解码,直接在硬件(例如GPU)中渲染。

有多种方式可以为Web应用程序提供CDM:

  • 用浏览器捆绑CDM。
  • 分开分配CDM。
  • 在操作系统中构建CDM。
  • 在固件中包含CDM。
  • 在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)并且正在开发其他解决方案。

相关技术#1:

媒体源扩展(MSE)

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的更多信息。

相关技术#2:

基于HTTP的动态自适应流媒体(DASH)

多设备,多平台,移动 - 无论您怎么称呼它,Web都经常在可变连接条件下体验。动态的自适应交付对于应对多设备领域的带宽限制和可变性至关重要。

DASH(也称为MPEG-DASH)旨在在片状世界中实现尽可能最佳的媒体传输,以实现流媒体和下载。其他一些技术可以做类似的事情 - 例如Apple的HTTP实时流媒体(HLS)和微软的平滑流媒体 - 但DASH是通过基于开放标准的HTTP进行自适应比特率流传输的唯一方法。DASH已被YouTube等网站使用。

这与EME和MSE有什么关系?基于MSE的DASH实现可以解析manifest,以适当的比特率下载视频片段,并在饥饿时将它们提供给视频元素 - 使用现有的HTTP基础架构。

换句话说,DASH使商业内容提供商能够对受保护内容进行自适应流式传输。

DASH做什么:

  • 动态: 响应变化的条件。
  • 自适应: 适应提供适当的音频或视频比特率。
  • 流媒体: 允许流媒体以及下载。
  • HTTP: 利用HTTP的优势实现内容交付,而没有传统流媒体服务器的缺点。

BBC已经开始使用DASH提供测试流

媒体以不同比特率编码多次。每种编码称为表示。这些被分成许多媒体分部。客户端通过从HTTP请求中按顺序请求分​​段来播放程序。表示可以分组为包含等同内容的表示的适应集。如果客户希望改变比特率,它可以从当前适配集合中选择一种替代方案,并开始从该表示中请求分段。内容以这种方式进行编码,以便客户端可以轻松地进行切换。除了一些媒体分部之外,一个表示通常还有一个初始化分段。这可以被认为是一个标题,包含关于编码,帧大小等的信息。

总结:

  1. 媒体以不同的比特率进行编码。
  2. 不同的比特率文件可从HTTP服务器获得。
  3. 客户端网络应用程序选择要使用DASH检索和回放的比特率。

作为视频分割过程的一部分,以编程方式构建称为媒体演示描述(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%的移动和桌面浏览器支持

进一步阅读

规格和标准

参考文章

Demos

GDB常用命令速查表

Essential Commands

简记 描述
gdbprogram [core] 调试core文件
b[file:] function 设置断点在函数上
rrun简写 运行你的程序
btbacktrace 显示程序栈
pexpr 打印显示表达式的值
ccontinue 继续运行程序
nnext 下一行 跳过函数调用
sstep 下一行 步入函数调用

Starting GDB

命令 描述
gdb 开启gdb 没有debug文件
gdbprogram 开始调试可执行文件
gdbprogram   core dump file 调试core是程序非法执行后core dump后产生的文件
gdb   --help 命令行帮助选项

Stoping GDB

命令 描述
quitq or EOF 退出GDB; eg C-d
INTERRUPT 终止当前命令,eg C-c

Getting Help

命令 描述
help 列出命令的类型
help class 命令类型的描述
help command 命令描述

Executing your Program

命令 描述
runarglist 使用arglist开始程序
run 使用当前参数列表开始程序
run   outf 使用input output开始程序
kill 结束运行程序
ttydev 使用dev作为标准输入输出为下一次的run
set args arglist 为下一次run指定arglist
set args 指定空参数列表
show args 显示参数列表
show argsvar 显示环境中var的值
set envvar string 设置环境变量var=string
unset envvar 移除环境变量var

Shell Commands

命令 描述
cddir 改变当前目录到dir
pwd 打印当前目录
make 调用 make
shellcmd 执行shell 命令串

Breakpoint and Watchpoints

命令 描述
break[file:] line 设置断点在fileline行处
b[file:] line eg: break mian.c :37.
break[file:] func 设置断点在filefunc
break +offset or -offset 设置断点在offset行距离当前停止
break*addr 在地址addr处设置断点
break...if   expr 设置条件断点在expr不为0的时候
tbreak   ... 临时断点 在到达后无效
rbreak[file:]regex 设置在所有函数匹配 regexfile
watchexpr 设置一个观察点对表达式expr
catchevent 设置在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个断点

Execution Control

命令 描述
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 运行程序直到当前函数完成返回,并打印函数返回时的堆栈地址返回值及参数值
jumpline 重新执行在指定的行数或地址
jump*   address

Display

命令 描述
print   [/f] [expr] 显示表达式expr的值
p   [/f] [expr] 格式化f 详细
x  [N u f] expr 查看内存的 /后可以指定格式化显示
whatis   查询变量或函数

Source Code

命令 描述
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在下面源码中

GDB Quick 查询卡片

GDB QUICK REFERENCE

参考资料

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 Promise

说明

我们都知道JavaScript是单线程的,这意味着任何两句代码都不能同时运行,它们得一个接一个来。在浏览器中,JavaScript 和其他任务共享一个线程,不同的浏览器略有差异,但大体上这些和 JavaScript共享线程的任务包括重绘、更新样式、用户交互等,所有这些任务操作都会阻塞其他任务。
作为人类,你是多线程的。你可以用多个手指同时敲键盘,也可以一边开车一遍电话。唯一的全局阻塞函数是打喷嚏,打喷嚏期间所有其他事务都会暂停。很烦人对么?尤其是当你开着车打着电话的时候。我们都不喜欢这样打喷嚏的代码。尤其是在浏览器环境中往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:

"同步模式"就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

而在异步模式中Promise的出现无疑是一件让人欢呼雀跃的事,这是为什么呢?

什么是Promise

一个promise代表了异步操作的最终结果,promise交互的主要方法是通过.then方法。Promise对象其实是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口

Promise对象有以下几种状态:

  • pending: 初始状态, 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.

这里要稍微拓展一点小知识,不知道你有没有听过薛定谔的猫。大体的意思是说在一个盒子里有一只猫,以及少量放射性物质。之后,有50%的概率放射性物质将会衰变并释放出毒气杀死这只猫,同时有50%的概率放射性物质不会衰变而猫将活下来。你永远的不会知道猫是死还是活在盒子未被打开前。这一点和promise的机制很相似,也就是说在异步回调返回之前,程序是无法知道结果是成功还是失败,因此我们给出了相应的预处理。

pending状态的promise对象既可转换为带着一个成功值的fulfilled 状态,也可变为带着一个失败信息的 rejected 状态。当状态发生转换时,promise.then绑定的方法(函数句柄)就会被调用。(当绑定方法时,如果 promise对象已经处于 fulfilled 或 rejected 状态,那么相应的方法将会被立刻调用, 所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。)
因为Promise.prototype.thenPromise.prototype.catch方法返回 promises对象, 所以它们可以被链式调用—— 一种被称为 composition 的操作。

Promise的常见用法

  • 与XMLHttpRequest结合使用
'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迷你书写的相当不错。
最后推荐一个不错的promiseES6-Promise

参阅

如何使用JavaScript检测Google Chrome浏览器隐身模式

image

原理

利用开启隐身模式的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)
        );
    }
}

How to use

isIncognito(function(itIs){
   if(itIs){
       console.log("我是隐身模式");
   }else{
       console.log("我不是隐身模式");
   }
});

JavaScript模块化编程简介

JavaScript 模块化编程

201206221140432
JavaScript本身不是一种模块化语言,设计者在创造JavaScript之初应该也没有想到这么一个脚本语言的作用领域会越来越大。以前一个页面的JS代码再多也不会多到哪儿去,而现在随着越来越多的JavaScript库和框架的出现,Single-page App的流行以及Node.js的迅猛发展,如果我们还不对自己的JS代码进行一些模块化的组织的话,开发过程会越来越困难,运行性能也会越来越低。因此,了解JS模块化编程是非常重要的。

为什么要使用模块化开发

可以看看requirejs官方的解释说的非常详细
why web modules

CommonJS

AMD

CMD

UMD

UMD是AMD和CommonJS的糅合,AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

下面看下一些经典库的封装基本都是基于UMD方式的

  • jquery
(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))
  • moment.js
;(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高手 像神一样使用编辑器

进入

进入不弹出窗口: 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

删除(Killing and Deleting)

移动实体 向后 向前
字符 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

参考

emacs refcard
emacs-in-one-year-guide

VP9解码器技术方案调研

背景

自2003年以来,H.264一直是最先进并被最广泛部署的视频压缩格式,也催生了HDTV、蓝光DVD、互联网视频网站(如YouTube,Twitch)等许许多多成功的商业产品。但是目前基于H.264协议的编码器已经达到了它们压缩性能的极限。特别是对于高清分辨率(1080p60)游戏内容的实时编码,这些编码器已经江郎才尽,耗尽了所有可挖掘的技术潜力。然而与此同时,新涌现出的下一代视频标准,VP9,HEVC和AV1。本文主要介绍VP9相对H.264对比的可实施性。

概念

具体可以查看wiki

浏览器支持程度以及覆盖范围

safas5

许多浏览器都支持VP9视频格式,图中可以看出Chrome和Firefox的支持程度比较好。截止2018年6月,约有4/5的浏览器(包括移动设备)支持WebM封装容器和VP9视频编码,例如Chromium、Chrome、Microsoft Edge、Firefox、Opera等浏览器都内置了VP9解码器,可在HTML5播放器中播放VP9影像格式。Windows 10操作系统也内置了WebM分离器和VP9解码器。

VP9 vs h264 vs h265

性能对比

参考paper《Performance Comparison of H.265/MPEG-HEVC, VP9, andH.264/MPEG-AVC Encoders》

下表显示了HM的参数配置。

配置信息

下表显示了VP9和x264的参数配置。

vp9_h264

使用的测试序列如下表所示

test

实验结果如下图所示

xx

下表显示了HEVC在同等质量的前提下(以PSNR为依据),相对于VP9和x264节约的码率。下表显示了所有序列的情况。总体来说HEVC相对于VP9节约了41.9%,HEVC相对于x264节约了38.9%。

11

下表显示了三种编码器整体性能的比较。表中百分比数字的意义是:同等视频质量的前提下,该列所属的编码器相对于该行所属的编码器节约的码率,如果为负值,则代表反而消耗了更多的码率。例如,同等质量的前提下,x264相对于VP9节约了8.4%的码率。

22

同等视频质量的前提条件下,编码消耗时间对比如下表所示。可以看出,VP9编码时间大约是x264的130倍。HEVC编码时间大约是VP9的7倍。
33

压缩率

来源于大量视频数据测试

具体可以看 视频

基本上能得出:与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

CryptoJS DES加密解密

背景介绍

由于之前业务需求需要对服务器返回的zip包进行解压、解密。 服务器使用的是Java DES加密算法需要前端来进行解密。目前浏览器端解密库里CryptoJS算得上是比较通用的库了。而且CryptoJS加密解密算法很多支持度比较广并且网上资料丰富可以有助与我们快速完成相关业务。下面以DES加密解密为例给大家介绍一下CryptoJS加密解密一些常见问题。

ArrayBuffer、TypeArray和WordArray

  • ArrayBuffer: 用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个ArrayBuffer中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的DataView,使用它们来读写缓冲区中的内容。
  • TypeArray:用来生成内存的视图,通过9个构造函数,可以生成9种数据格式的视图,比如Uint8Array(无符号8位整数)数组视图, Int16Array(16位整数)数组视图, Float32Array(32位浮点数)数组视图等等
  • WordArray: 这个在JavaScript中没有任何描述是CryptoJS实现的一种数据结构。CryptoJS中大量使用的数据结构,是一个代表32位无符号数组对象,当你传入字符串时CryptoJS会自动包装成utf8编码的WordArray。可以使用CryptoJS.enc.Utf8工具来转换。

总结: ArrayBuffer对象代表原始的二进制数据,TypedArray对象代表确定类型的二进制数据,DataView对象代表不确定类型的二进制数据。它们支持的数据类型一共有9种。WordArray对象是CryptoJS内部实现的一种二进制数据结构用32位数组表示。具体实现感兴趣可以参考WordArray源码

简单字符串DES加解密

对于字符串加解密比较简单我们可以直接看官方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);
}

ArrayBuffer DES加解密

考虑到平时项目中大部分是加载一个具体的文件流进行加解密这种场景比较多所以重点讲一下。这部分官方是没有给出说明需要自己摸索。

WordArray和uInt8Array转换

由于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);
  }
};

DES加密

// 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;
};

DES解密

// 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会有问题。

注意点

  • 服务端Java加密浏览器端解密
    对于这种情况一点要保证CryptoJS使用的的模式modepadding方式和Java的一致,例如ECB mode和PKCS5Padding。由于CryptoJS只有Pkcs7 经测试Pkcs7可以兼容PKCS5Padding。具体对于mode padding感兴趣的同学可以自行google。
  • 报错_Error: Malformed UTF-8 data_
    这个错误出现说明你解密后的数据可能出现乱码或者解密密钥错误解出来的数据不对等等情况比较多。总之就是没解对就是了,这个时候需要自己检测下流程上有没有问题,密钥是否正确。也可以和服务端加密同学讨论一下他们加密流程 看是不是先进行base64加密后使用DES加密。
  • WordArray
    由于我们加解密一般都是以文件的形式进行,所以加密时候需要把Uint8Array二进制数据转成WordArray传进来加密 ,解密的时候需要把传进来的Uint8Array转成WordArray再转成base64字符串来解密。

参考资料

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/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.