Coder Social home page Coder Social logo

blog's Introduction

具体内容请看 issues

blog's People

Contributors

bigkongfupanda avatar shen-zhao avatar

Watchers

 avatar  avatar

Forkers

shen-zhao

blog's Issues

generator 解密之二:为什么说 async/await 是 generator 的语法糖

实现 async/await 必须要使用 Promise,因为 async 函数返回的是一个 Promise 对象。

使用 async/await 实现一个 sleep 的功能

function sleep(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1);
    }, time);
  });
}

async function test () {
  for(let i = 0; i < 10; i++) {
    let result = await sleep(1000);
    console.log(result);
  }
}

async/await 转成 generator 和 promise 来实现:

let test = function () {
  // ret 为一个Promise对象,因为 async 函数的返回值必须是一个 promise 对象
  let ret = _asyncToGenerator(function* () {
    for (let i = 0; i < 10; i++) {
      let result = yield sleep(1000);
      console.log(result);
    }
  });
  return ret;
}();

function _asyncToGenerator(genFn) {
  return new Promise((resolve, reject) => {
    let gen = genFn();
    function step(key, arg) {
      let info = {};
      try {
        info = gen[key](arg);
      } catch (error) {
        reject(error);
        return;
      }
      if (info.done) {
        resolve(info.value);
      } else {
        return Promise.resolve(info.value).then((v) => {
          return step('next', v);
        }, (error) => {
          return step('throw', error);
        });
      }
    }
    step('next');
  });
}

前端模块化

1 why

为什么需要模块化

2 实现的方式

3 有哪些成熟的解决方案

AMD,CMD,commonJS,ES6 模块

4 异同之处

5 实现的原理

6 如何解决循环依赖

object扩展

目录

  • 属性的简洁表示法
  • Object新增的方法

1. 属性的简洁表示法

如果属性和属性名相同,则可以写成一个,另外,函数作为属性的时候,可以省略 function 关键字,如下所示。

let birth = '2000/01/01';
const Person = {
  name: '张三',
  // 等同于 birth: birth
  birth,
  // 等同于 hello: function(){}
  hello () {
    console.log('我的名字叫', this.name);
  }
}

2. Object新增的方法

Object.is()

比较两个值是否相等。ES5 中有两个运算符 =====。但是他们呢都有缺点,前者会自动转换数据类型,后者的 NaN 不等于自身,以及 -0 等于 +0
ES6 中用来严格表两个值是否相等,与 === 基本一致。

Object.is({}, {}); // false
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false

generator 解密之一: generator 的应用

generator 实现 ajax 异步操作

function* gen() {
  let res = yield request(url);
  console.log(res);
}

function request(url) {
  getJSON(url, res => {
    // 此处调用next的时候,必须带上res,作为yield的返回值
    it.next(res);
  });
}

let it = gen();

it.next();

给普通对象部署 Iterator 接口

利用 Generator 函数,可以在任意对象上部署 Iterator 接口。

let obj = {name: 'zhansan', age: 13};

function* genEntries(obj) {
  let keys = Object.keys(obj);

  for (const key of keys) {
    yield [key, obj[key]];
  }
}

for (const [key, value] of genEntries(obj)) {
  console.log(key, value);
}
// name zhansan
// age 13

或者给 Object 对象添加 [Symbol.iterator] 属性,[Symbol.iterator] 属性值是一个函数,让其等于一个 generator 函数即可

let obj = {name: 'zhangsan', age: 18};

function setEnties(obj) {
  obj[Symbol.iterator] = function* () {
    let keys = Object.keys(obj);
    for(let i = 0; i < keys.length; i++) {
      yield obj[keys[i]];
    }
  }
}

setEnties(obj);

for (const i of obj) {
  console.log(i);
}

// zhangsan
// 18

async和defer对script脚本加载的影响

直接使用script脚本的话,html会按照顺序来加载并执行脚本,在脚本加载&执行的过程中,会阻塞后续的DOM渲染。
这是因为 UI 渲染线程与 JS 引擎是互斥的,当 JS 引擎执行时 UI 线程会被挂起。

defer

如果script标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续 DOM 的渲染;
如果有多个设置了 defer 的script标签存在,则会按照顺序执行所有的script;
defer 脚本会在文档渲染完毕后, DOMContentLoaded 事件调用前执行。

async

async 的设置,会使得script脚本异步的加载并在允许的情况下执行。
async 的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。
DOMContentLoaded 事件的调用不会管带有 async 属性的script脚本是否加载完和执行完

注意点

带有 asyncdefer 的脚本的下载是和 HTML 的下载与解析是并行的,但是 JS 的执行一定是和 UI 线程是互斥的,像下面这张图所示,async 在下载完毕后的执行会阻塞HTML的解析

image
他们有两处不同:

项目 async defer
顺序 带有 async 的脚本是优先执行先加载完的脚本,他们在页面中的顺序并不影响执行的顺序 带有 defer 的脚本按照他们在页面中出现的顺序依次执行
DOMContentLoaded 带有 async 的脚本也许会在页面没有完全下载完之前就加载,这种情况会在脚本很小或本地缓存,并且页面很大的情况下发生 带有 defer 的脚本会在页面加载和解析完毕后执行,刚好在 DOMContentLoaded 之前执行

defer 是会阻塞 DOMContentLoaded 的,被 defer 的脚本要在 DOMContentLoaded 触发前执行,所以如果HTML很快就加载完了(先不考虑 CSS 阻塞 DOMContentLoaded 的情况),而 defer 的脚本还没有加载完,浏览器就会等,等到脚本加载完,执行完,再触发 DOMContentLoaded

外部样式表并不会阻塞 DOM 的解析,所以 DOMContentLoaded 并不会被它们影响。不过仍然有一个陷阱:如果在样式后面有一个内联脚本,那么脚本必须等待样式先加载完。

简单来说,JS 因为有可能会去获取 DOM 的样式,所以 JS 会等待样式表加载完毕,而 JS 是阻塞 DOM 的解析的,所以在有外部样式表的时候,JS 会一直阻塞到外部样式表下载完毕

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // 脚本直到样式表加载完毕后才会执行。
  alert(getComputedStyle(document.body).marginTop);
</script>

发生这种事的原因是脚本也许会像上面的例子中所示,去得到一些元素的坐标或者基于样式的属性。所以他们自然要等到样式加载完毕才可以执行。

DOMContentLoaded 需要等待脚本的执行,脚本又需要等待样式的加载。

HTML 页面的生命周期

HTML页面的生命周期有以下三个重要事件:

  • DOMContentLoaded:浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是像 <img> 和样式表等外部资源可能并没有下载完毕;
  • load:浏览器已经加载了所有的资源(图片,样式表等);
  • beforeunload/unload:当用户离开页面的时候触发

每个时间都有特定的用途:

  • DOMContentLoaded:DOM 加载完毕,所以 JS 可以访问所有的 DOM 节点,初始化界面;
  • load:附加资源已经加载完毕,可以在此事件触发时获得图像的大小(如果没有被在 HTML/CSS 中指定);
  • beforeunload/unload:用户正在离开页面,可以询问用户是否保存了更改以及是否确定要离开页面。

参考资料

[译]页面生命周期:DOMContentLoaded, load, beforeunload, unload解析
css加载会造成阻塞吗?
浅谈script标签中的async和defer

这一次彻底搞懂 "x == y" 的判断逻辑

javascript 是弱类型语言,在进行 == 运算的时候 ,如果等号两边的数值类型不同的时候,会进行类型转换。

1. 类型转换的逻辑算法

关于如何进行类型转换,ECMA 的标准里面有说明,如下所示:

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
1. If Type(x) is the same as Type(y), then
    - If Type(x) is undefined, return true;
    - If Type(x) is null, return true;
    - If Type(x) is Number, then
        - If x is NaN, return false;
        - If y is NaN, return false;
        - If x is the same Number value as y, return true;
        - If x is +0 and y is -0, return true;
        - If x is -0 and y is +0, return true;
        - Return false.
    - If Type(x) is String, then return true if x and y are exactly the same sequence of characters(same length and same characters in corresponding positions). Otherwise, return false;
    - If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false;
    - Return true if x and y refer to the same object. Otherwise, return false;

2. If x is null and y is undefined, return true;
3. If x is undefined and y is null, return true;
4. If Type(x) is Number and Type(y) is String, return result of the comparison of x == ToNumber(y);
5. If Type(x) is String and Type(y) is Number, return result of the comparison of ToNumber(x) == y;
6. If Type(x) is Boolean, return result of the comparison of ToNumber(x) == y;
7. If Type(y) is Boolean, return result of the comparison of x == ToNumber(y);
8. If Type(x) is either String or Number and Type(y) is Object, return result of the comparison of x == ToPrimitive(y);
9. If Type(x) is Object and Type(y) is either String or Number, return result of the comparison of ToPrimitive(x) == y;
10. Return false.

归纳起来,是按照以下的顺序来执行判断逻辑的:

  1. 首先判断 xy 的数据类型,如果数据类型相同,则判断值是否相同,如果相同则为 true , 否则为 false。其中需要注意的是 Number 类型,如果 x 和 y,二者中至少有一个为 NaN,则为 false;。如果类型不同,则会进行类型转换。
  2. 如果 xy,二者中一个为 null ,一个为 undefined ,则为 true
  3. 如果 xy 中,一个是 Number 类型,一个是 String 类型,则将 String 类型的数据转换为 Number 类型;
  4. 如果 xy 中,一个是 Boolearn 类型,则将 Boolearn 类型的数据转换为 Number 类型,true -> 1false -> 0
  5. 如果 xy,二者中一个为 Object ,另一个为 NumberString ,则为会调用 ToPrimitive(Object),然后再将其返回值跟另一个值进行比较;
  6. 如果不满足上述的判断,则返回 false 。除了 nullundefined 以外,其他类型的数据,与 nullundefined
    进行 == 比较时,直接返回 false

2. ToPrimitive

上述中,当 xy 中有一个是 Object 类型时,会先进行 ToPrimitive() 的运算,于是继续查找 ToPrimitive() 的标准。

7.1.1 ToPrimitive ( input [ , PreferredType ] )

The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. The abstract operation ToPrimitive converts its input argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favour that type. Conversion occurs according to the following algorithm:

  1. Assert: input is an ECMAScript language value.
  2. If Type(input) is Object, then
    • If PreferredType is not present, let hint be "default".
    • Else if PreferredType is hint String, let hint be "string".
    • Else PreferredType is hint Number, let hint be "number".
    • Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
    • If exoticToPrim is not undefined, then
      • Let result be ? Call(exoticToPrim, input, « hint »).
      • If Type(result) is not Object, return result.
      • Throw a TypeError exception.
    • If hint is "default", set hint to "number".
    • Return ? OrdinaryToPrimitive(input, hint).
  3. Return input.

When ToPrimitive is called with no hint, then it generally behaves as if the hint were Number. However, objects may over-ride this behaviour by defining a @@toPrimitive method. Of the objects defined in this specification only Date objects (see 20.3.4.45) and Symbol objects (see 19.4.3.4) over-ride the default ToPrimitive behaviour. Date objects treat no hint as if the hint were String.

关于 OrdinaryToPrimitive(input, hint),标准如下:

7.1.1.1 OrdinaryToPrimitive ( O, hint )

When the abstract operation OrdinaryToPrimitive is called with arguments O and hint, the following steps are taken:

  1. Assert: Type(O) is Object.
  2. Assert: Type(hint) is String and its value is either "string" or "number".
  3. If hint is "string", then
    • Let methodNames be « "toString", "valueOf" ».
  4. Else,
    • Let methodNames be « "valueOf", "toString" ».
  5. For each name in methodNames in List order, do
    • Let method be ? Get(O, name).
    • If IsCallable(method) is true, then
      • Let result be ? Call(method, O).
      • If Type(result) is not Object, return result.
  6. Throw a TypeError exception.

看了以上的标准,关键信息总结如下:

  • 如果传入的 hintString ,先判断 toString 能否调用,再判断 toString() 的结果,是基本类型才返回,再判断 valueOf 能否调用,再判断 valueOf() 的结果,是基本类型才返回,否则报错。
  • 如果传入的 hintNumber(或者没有 hint ,默认是 Number ),先判断 valueOf ,再判断 toString
  • 对于普通 Object ,默认用 hintNumber 的方式来转换,对于 Date 类型的 Object ,用 hintString 的方式来转换。
  • 同时,因为 ObjectvalueOftoString 方法有可能会被重写,所以会调用重写后的 valueOftoString

3. 常见 Object 类型的 valueOf() 和 toString() 返回值

// 普通 object
let obj1 = {};
let obj2 = {a: 1, b: 'obj', c: function(){}, d: undefined, e: null};
let obj3 = new Object('test');
let obj4 = new Object(1);
let obj5 = new Object({a: 1});
// 普通 object 的 valueOf
obj1.valueOf(); // {}
obj2.valueOf(); // {a: 1, b: 'obj', c: function(){}, d: undefined, e: null}
obj3.valueOf(); // test
obj4.valueOf(); // 1
obj5.valueOf(); // {a: 1}
// 普通 object 的 toString
obj1.toString(); // "[object Object]"
obj2.toString(); // "[object Object]"
obj3.toString();// "test"
obj4.toString();// "1"
obj5.toString();// "[object Object]"

// 数组
let arr1 = [];
let arr2 = [undefined];
let arr3 = [null];
let arr4 = [1, '1'];
let arr5 = [{a: 1}, {fn: function(){}}];
// 数组的 valueOf
arr1.valueOf(); // []
arr2.valueOf(); // [undefined]
arr3.valueOf(); // [null]
arr4.valueOf(); // [1, '1']
arr5.valueOf(); // [{a: 1}, {fn: function(){}}]
// 数组的 toString
arr1.toString(); // ""
arr2.toString(); // ""
arr3.toString(); // ""
arr4.toString(); // "1,1"
arr5.toString(); // "[object Object],[object Object]"

// 函数
let fn = function(){};
// 函数默认的 valueOf
fn.valueOf(); // function(){}
// 函数默认的 toString
fn.toString(); // "function(){}"

// 函数的 valueOf 和 toString 方法可以被改写
let fn1 = function() {};
fn1.valueOf = function(){
  return '改写后的valueOf';
};
fn1.toString = function(){
  return '改写后的toString';
};
fn1.valueOf(); // "改写后的valueOf"
fn1.toString(); // "改写后的toString"

4. 实例

[] == ![]; // true
// 判断逻辑:首先 ![] 为 Boolean 类型,等号两边的数据类型不同,会将 ![] 进行类型转换,因为 ![] 为 false,所以转为数值类型 0, 此时变为了 [] == 0,调用 [] 的 ToPrimitive 方法,先执行 [].valueOf(),返回值为 [],不是基本数据类型,从而继续调用 [].toString(),返回值为 "",此时等式变为了 "" == 0,然后将 "" 空字符串转换为数值类型 0,从而等式变为了 0 == 0,为 true。
[] == []; // false
[] == {}; // false

'' == '0'; // false
'' == 0; // true

false == 'false'; // false
false == '0'; // true
false == undefined; // false
false == null; // false
undefined == null; // true

{} + 1; // 1
// 有人会认为答案应该是'[object Object]1',因为对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString(),发现是'[object Object]',返回这个值,然后再相加;但是问题出在编译器会认为前面的{}是一个代码块,后面是一元操作符加号和1,所以结果为1

{} + '1'; // 1
// 没错,这里证明了这个加号是一元的,将'1'转化为了number

var a = {};
a + 1; // '[object Object]1'
// 老铁,这次就对了

[1,2] + 1; // '1,21' 
// [1,2],先对Object进行ToPrimary(),先看valueOf(),发现是自身,不是基本类型,再看toString,Array的toString()相当于join(','),所以得到'1,2'再和1相加得到'1,21'

// 普通的Object
var a = {}; // 普通类型的ToPrimitive会遵从hint Number的转换规则
a.toString = () => 100;
a.valueOf = () => '10';

a + 2; // '102' -> 相当于 '10' + 2 为 '102'
a + '2'; // '102'
a > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true 
a > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
a == 100; // false -> 相当于 '10' == 100为false

// Date类型的Object
var b = new Date(); // Date类型的ToPrimitive会遵从hint String的转换规则
b.toString = () => 100; // 这里的搞怪为了测试
b.valueOf = () => '10';

b + 2; // 102 -> 加号是ToPrimitive(), 100 + 2 为102
b + '2'; // '1002' -> 相当于 100 + '2'为'1002'
b > 3; // true -> 进行ToPrimitive() hint为Number操作之后 -> 比较 '10' > 3 -> 不都是String类型,对'10'进行ToNumber(), 10 > 3为true 
b > '3'; // false -> 实际比较的是 '10' > '3' 第一个字符的ascii码小,直接为false
b == 100; // true -> 进行ToPrimitive()操作

实现下面的题目:

// 实现一个add方法
function add() {
    // ...
}

// 满足以下类型的调用和计算
add(1)(2)(3) + 1; // 7

var addTwo = add(2);
addTwo + 5; // 7
addTwo(3) + 5; // 10
add(4)(5) + add(2); // 11

答案:

function add (n) {
  function fn (x) {
    return add(n + x);
  }
  fn.valueOf = function(){
    return n;
  }
  return fn;
}

参考资料:

ECMA-262 edition 9

深入理解Javascript中Object类型的转换

前端使用Blob和File读取文件

目录

  • Blob及其属性和方法
  • File
  • FileReader及其属性和方法
  • URL及其属性和方法

历史上,JavaScript 无法处理二进制数据。如果一定要处理的话,只能使用 charCodeAt() 方法,一个个字节地从文字编码转成二进制数据,还有一种办法是将二进制数据转成 Base64 编码,再进行处理。这两种方法不仅速度慢,而且容易出错。ECMAScript 5 引入了 Blob 对象,允许直接操作二进制数据。

Blob 对象是一个代表二进制数据的基本对象,在它的基础上,又衍生出一系列相关的API,用来操作文件。

  • File对象:负责处理那些以文件形式存在的二进制数据,也就是操作本地文件;
  • FileList对象:File对象的网页表单接口;
  • FileReader对象:负责将二进制数据读入内存内容;
  • URL对象:用于对二进制数据生成URL

Blob

Blob (Binary Large Object) 对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的API(比如 File 对象),都是建立在 Blob 对象基础上的,继承了它的属性和方法。

创建 Blob 类型的对象

生成 Blob 对象有两种方法:一种是使用 Blob 构造函数,另一种是对现有的 Blob 对象使用 slice 方法切出一部分。

1. new Blob()

Blob 构造函数,接受两个参数。第一个参数是一个包含实际数据的数组,这个数组中的元素可以为 DOMString ,或者 ArrayBuffer ,第二个参数是数据的类型,这两个参数都不是必需的。

var domstring = '<div>Hello world</div>';
var blob1 = new Blob([domstring], {type: 'text/html'});

var buffer = new ArrayBuffer(8);
var blob2 = new Blob([buffer], {type: 'text/plain'});

2. blob.slice()

此方法返回一个新的 Blob 对象,包含了原 Blob 对象中指定范围内的数据

Blob.slice(start:number, end:number, contentType:string)。start:开始索引,默认为0;end:截取结束索引(不包括end);contentType:新Blob的MIME类型,默认为空字符串

Blob 对象的属性

  • Blob.size: Blob 对象中所包含数据的大小(字节)。该属性为只读;
  • Blob.type: 一个字符串,表明该 Blob 对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。该属性为只读

Blob 应用实例

下面是一个使用XMLHttpRequest对象,将大文件分割上传的例子。

function upload(blobOrFile) {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/server', true);
  xhr.onload = function(e) {};
  xhr.send(blobOrFile);
}

document.querySelctor('input[type="file"]').addEventListener('change', function() {
  let blob = this.files[0];
  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk size
  const SIZE = blob.size;
  let start = 0;
  let end = BYTES_PER_CHUNK;
  while(start < end) {
    upload(blob.slice(start, end));
    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

File

File 接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。

File 对象可以用来获取某个文件的信息,还可以用来读取这个文件的内容。通常情况下, File 对象是来自用户在一个 <input> 元素上选择文件后返回的 FileList 对象,也可以是来自由拖放操作生成的 DataTransfer 对象。

用户在选择一个或者多个文件后,可以通过 File API访问这些File 对象,这些对象被包含在一个 FileList 对象中。所有 typefileinput 都有一个 files 属性,通过 Element.files 可以返回 FileList 对象。

<body>
    <input type="file" id="fileInput" name="file" multiple="multiple" accept="image/*">
    <script>
        var fileInput = document.querySelector("#fileInput");
        fileInput.addEventListener("change", function (event) {
          // 文件列表对象
          var fileList = this.files;
          // 获取第一个文件
          var file = fileInput.files[0];
          // 文件名
          var filename = file.name;
          // 文件大小
          var filesize = file.size;
          //文件的 MIME 类型,如果分辨不出类型,则为空字符串,该属性只读
          var type = file.type; 
          // 文件的上次修改时间,格式为时间戳
          var lastModified = file.lastModified;
          // 文件的上次修改时间,格式为 Date 对象实例
          var lastModifiedDate = file.lastModifiedDate;
        }, false);

        console.log(fileList);
        // 上传了两个文件,FileList {0: File, 1: File, length: 2}
    </script>
</body>

File 对象的属性:

  • name:文件名,该属性只读。
  • size:文件大小,单位为字节,该属性只读。
  • type:文件的 MIME 类型,如果分辨不出类型,则为空字符串,该属性只读。
  • lastModified:文件的上次修改时间,格式为时间戳。
  • lastModifiedDate:文件的上次修改时间,格式为 Date 对象实例

File 对象的方法:

File 接口没有定义任何方法,但是它从 Blob 接口继承了以下方法:

Blob.slice([start[, end[, contentType]]])

返回一个新的 Blob 对象,它包含有源 Blob 对象中指定范围内的数据。

FileReader

我们知道 Blob 对象只是二进制数据的容器,本身并不能操作二进制,FileReader 对象就是专门操作二进制数据的,FileReader 主要用于将文件内容读入内存,通过一系列异步接口,可以在主线程中访问本地文件。

构造函数

var reader = new FileReader();

属性

  • FileReader.error:表示在读取文件时发生的错误;
  • FileReader.readyState:0-还没有加载任何数据, 1-数据正在被加载, 2-已完成全部的读取请求;
  • FileReader.result:文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。

事件

  • FileReader.onabort:处理 abort 事件。该事件在读取操作被中断时触发;
  • FileReader.onerror:处理 error 事件。该事件在读取操作发生错误时触发;
  • FileReader.onload:处理 load 事件。该事件在读取操作完成时触发;
  • FileReader.onloadstart:处理 loadstart 事件。该事件在读取操作开始时触发;
  • FileReader.onloadend:处理 loadend 事件。该事件在读取操作结束时(要么成功,要么失败)触发;
  • FileReader.onprogress:处理 progress 事件。该事件在读取 Blob 时触发

方法

  • FileReader.abort():中止读取操作。在返回时,readyState 属性为DONE;
  • FileReader.readAsArrayBuffer():开始读取指定的 Blob 中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象;
  • FileReader.readAsBinaryString():开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含所读取文件的原始二进制数据, 该方法将文件读取为二进制字符串,通常我们将它传送到后端,后端可以通过这段字符串存储文件;
  • FileReader.readAsDataURL():开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个 data: URL 格式的字符串以表示所读取文件的内容;
  • FileReader.readAsText():开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个字符串以表示所读取的文件内容, 该方法有两个参数,其中第二个参数是文本的编码方式,默认值为 UTF-8。这个方法非常容易理解,将文件以文本方式读取,读取的结果即是这个文本文件中的内容。

实际应用

上传图片后直接进行预览,而不用先经过后台。

var input  = document.getElementById("file"); //input file
input.onchange = function(){
    var file = this.files[0];
    if(!!file){
        var reader = new FileReader();
        // 将图片转成DataURL格式
        reader.readAsDataURL(file);
        reader.onload = function(){
            //读取完毕后输出结果
           document.getElementById("file_img").src = reader.result //显示上传的图片
           console.log(reader.result);
        }
    }
}

URL

URL.createObjectURL(blob)

//blob参数是一个File对象或者Blob对象.
var objecturl =  window.URL.createObjectURL(blob);

上面的代码会对二进制数据生成一个 URL ,这个 URL 可以放置于任何通常可以放置 URL 的地方,比如 img 标签的 src 属性。需要注意的是,即使是同样的二进制数据,每调用一次 URL.createObjectURL 方法,就会得到一个不一样的 URL

这个 URL 的存在时间,等同于网页的存在时间,一旦网页刷新或卸载,这个 URL 就失效。(File 和 Blob 又何尝不是这样呢)除此之外,也可以手动调用 URL.revokeObjectURL 方法,使 URL 失效。

URL.revokeObjectURL(objectURL);

当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

实际应用

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
    <script src="./lib/jquery.js"></script>
    <style type="text/css">
        #file{
            display: block;
            width: 400px;
            height: 300px;
            opacity:0;
            margin: -300px 0 0 0;            
        }
        #preview{
            display: block;
            width: 400px;
            height: 300px;
        }
    </style>
</head>
<body>
    <img id="preview" src="" alt="点击上传图片"/>
    <input type="file" id="file" />
</body>
</html>
<script type="text/javascript">
  $('#file').on('change', function(){
    //获取文件列表对象
    var fileList = $('#file')[0].files;
    //创建文件流获取文件地址
    var src = window.URL.createObjectURL(fileList[0]);     
    //设置图片路径  
    $("#preview").attr("src", src);
    $("#preview").load(function() {
      window.URL.revokeObjectURL(src);
    });
 });
</script>

参考资料

Blob - Web API 接口参考 | MDN

Blob,FileReader全面解析

generator 解密之三:generator 执行器

Generator 是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,自动交回执行权。

两种方法可以实现执行器:

  • 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权;
  • Promise 对象。将异步操作包装成 Promise 对象,用 then 方法中交回执行权

Thunk 函数实现 generator 执行器

在js中实现异步的方式为回调函数,比如读取文件的操作:

fs.readFile(path, function callback(err, data) {
  console.log(data);
});

上面的 fs.readFile 有两个参数,第一个路径 path,第二个为回调函数;如果将 readFile 函数改成单参数的函数,类似于函数柯里化,那么结果如下:

function thunkFile(path) {
  return function(cb) {
    return fs.readFile(path, cb)
  }
}

经过转换,变为了单参数的函数,每次只接受一个参数,调用 thunkFile 函数读取文件:

let thunk = thunkFile('./a.txt');
thunk(function(err, data) {
  if(err) return;
  console.log(data);
});

其中 thunk 就是所谓的 thunk 函数。所谓的 thunk 函数也就是接受一个回调函数为参数的函数。

任何接受回调函数为参数的函数,都可以写成 thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。

// es5版本
function Thunk(fn) {
  return function() {
    let args = [...arguments];
    return function(cb) {
      args.push(cb);
      return fn.apply(this, args);
    };
  }
}

generator 要想实现异步操作,可以在 yield 后面接上异步操作的表达式,但是问题是如何才能够保证前一步 yield 执行完了再执行下一步 yield 呢,仅仅自执行肯定是不行的,例如下面的自执行函数

function* gen() {
  let result1 = yield readFile('./a.txt');
  let result2 = yield readFile(`${result1}.txt`);
  console.log(result1);
  console.log(result2);
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}

上面代码中,Generator 函数gen会自动执行完所有步骤。但是,这不适合异步操作。因为 result1 还没有返回,第二个 yield 可能就已经执行了。必须要让 第二个yield 拿到了第一个 yield 的结果之后再执行,这就要求必须在第一个 yield 的回调函数中(因为回调函数中有结果)交回函数的执行权,即执行 g.next(),这样才能够保证第一个 yield 有了结果之后再执行第二个 yield。

按照这个思路来手动执行上面的 generator 函数:

let g = gen();
let info1 = g.next();
info1.value(function(err, data) {
  if(err) throw err;
  let info2 = g.next(data);
  info2.value(function(err, data) {
    if(err) throw err;
    g.next(data)
  });
})

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。

到这里,可以自己写一个通用的基于 Thunk 函数的 generator 执行器。

function run(genFn) {
  let gen = genFn();
  function next(err, data) {
    let result = gen.next();
    if(result.done) return result.value;
    result.value(next);
  }
  next();
}

上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next 方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

有了这个执行器,执行 Generator 函数方便多了。不管内部有多少个异步操作,直接把 Generator 函数传入run函数即可。当然,前提是每一个异步操作,都要是 Thunk 函数,也就是说,跟在yield命令后面的必须是 Thunk 函数。

var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  // ...
  var fn = yield readFileThunk('fileN');
};

run(g);

Thunk 函数并不是实现 generator 函数自执行的唯一办法,因为自执行的关键是,必须有一种机制,自动控制 generator 函数的流程,接收和交换函数的执行权。回调函数可以做到这一点, Promise 也可以。

Promise 实现 generator 执行器

Promise 实现 generator 执行器的原理是:将异步操作包装成 Promise 对象,用 then 方法中交回执行权

例如文件读取是异步I/O操作,先将读取操作包装成一个promise对象,然后使用then来获取执行权。

let fs = require('fs');
function readFile(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
      if(err) return reject(err);
      resolve(data); 
    });
  });
}

function* genReadFile() {
  let f1 = yield readFile('./a.txt');
  let f2 = yield readFile('./b.txt');
}

基于 promise 的 generator 自执行器。

function run(genFn) {
  let gen = genFn();

  function next(data) {
    let result = gen.next(data);
    result.value.then(_data => {
      next(_data);
    });
  }

  next();
}

// 调用执行器
run(genReadFile);

总结

实现异步 generator 执行器的关键是要确保上一个 yield 返回结果了之后,再继续调用生成器对象的 next() 方法执行下一个 yield。对于异步操作来说,只有在回调函数或者 promise.then 中可以保证当前的异步执行完毕有了结果,所以有了基于 Thunk 函数和promise 对象这两种方式的 generator 执行器。

故,要使用 generator 执行器的话,generator 中的 yield 后面必须接 Thunk 函数或者 promise 对象才行。

ES6学习笔记 - 1. let & const

// 1 基本用法
// let 为块级作用域,经典用法为 for 循环

/* 如果一下代码使用var,则最后输出的是10 */
var a = [];
for (var i =1; i < 10; i++) {
a[i] = function(){
console.log(i);
};
}
a6; // 10

/如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。/
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function(){
console.log(i);
};
}
a6; // 6
/* 上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算*/

//另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for(let i = 0; i < 3; i++){
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
/* 上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。 */

// 2. 不存在变量提升

// var 的情况
console.log(foo); // 输出 undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

/** 上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。 */

// 3. 暂时性死区

var tmp = 123;

if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
/** 上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

  • ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
  • 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)
    */

//有些“死区”比较隐蔽,不太容易发现。
function bar(x = y, y = 2) {
return [x, y];
}

bar(); // 报错
/** 上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了 */

generator 解密之一: generator 的应用

generator 实现 ajax 异步操作

function* gen() {
  let res = yield request(url);
  console.log(res);
}

function request(url) {
  getJSON(url, res => {
    // 此处调用next的时候,必须带上res,作为yield的返回值
    it.next(res);
  });
}

let it = gen();

it.next();

给普通对象部署 Iterator 接口

利用 Generator 函数,可以在任意对象上部署 Iterator 接口。

let obj = {name: 'zhansan', age: 13};

function* genEntries(obj) {
  let keys = Object.keys(obj);

  for (const key of keys) {
    yield [key, obj[key]];
  }
}

for (const [key, value] of genEntries(obj)) {
  console.log(key, value);
}
// name zhansan
// age 13

或者给 Object 对象添加 [Symbol.iterator] 属性,[Symbol.iterator] 属性值是一个函数,让其等于一个 generator 函数即可

let obj = {name: 'zhangsan', age: 18};

function setEnties(obj) {
  obj[Symbol.iterator] = function* () {
    let keys = Object.keys(obj);
    for(let i = 0; i < keys.length; i++) {
      yield obj[keys[i]];
    }
  }
}

setEnties(obj);

for (const i of obj) {
  console.log(i);
}

// zhangsan
// 18

Object.create、Object.setPrototypeOf 设置继承关系

Object.create(superObj)

Object.create() 用于从原型对象(superObj)上生成新的实例对象(subObj),其实质是 subObj.__proto__ = superObj,则 subObj 也能够读取到 superObj 的属性。返回值是一个新对象。

let superObj = {name: 'super'};
let subObj = Object.create(superObj);
console.log(subObj.name); // super
console.log(subObj.__proto__ === superObj); // true

Object.create() 其实可以接受两个参数,第二个参数表示要添加到新创建对象的属性。注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。

const o = Object.create({}, {
  p: {
      value: 42,
      enumerable: false,
      // 该属性不可写
      writable: false,
      configurable: true
  }
});
o.p = 24;
console.log(o.p); // 42

Object.create 函数的实现原理:

Object.create = function(obj, props) {
  function F() {};
  F.prototype = obj;
  let newObj = new F();
  Object.assign(newObj, props);
  return newObj;
};

Object.setPrototypeOf(subObj, superObject)

Object.setPrototypeOfObject.create 的作用是一样的,只不过 Object.setPrototypeOf 是给现有的对象设置原型,返回一个新对象,接受两个参数。

let subObj = {age: 19};
let superObj = {name: 'tian'};
Object.setPrototypeOf(subObj, superObj);

console.log(subObj.name); // tian
console.log(subObj.__proto__ === superObj); // true

Object.getPrototypeOf(obj)

Object.getPrototypeOf 方法返回一个对象的原型。这是获取原型对象的标准方法。

let obj = {};
Object.getPrototypeOf(obj); // Object.prototype

function fn() {};
Object.getPrototypeOf(fn); // Function.prototype

nodejs 实现登录总结

sessionId , token
Token的概念
NodeJS 实现基于 token 的认证应用

https://blog.csdn.net/guodaoying/article/details/51779675
1.是不是只要一打开一个页面就会产生一个jsessionid? 2.在不关闭浏览器的情况下,什么时候jsessionid会改变?我登陆后,登陆然后退出,jsessionid会有什么变化? 3.session和jsessionid有什么关系? 谢谢!

所谓session可以这样理解:当与服务端进行会话时,比如说登陆成功后,服务端会为用户开壁一块内存区间,用以存放用户这次会话的一些内容,比如说用户名之类的。那么就需要一个东西来标志这个内存区间是你的而不是别人的,这个东西就是session id(jsessionid只是tomcat中对session id的叫法,在其它容器里面,不一定就是叫jsessionid了。),而这个内存区间你可以理解为session。 然后,服务器会将这个session id发回给你的浏览器,放入你的浏览器的cookies中(这个cookies是内存cookies,跟一般的不一样,它会随着浏览器的关闭而消失)。 之后,只有你浏览器没有关闭,你每向服务器发请求,服务器就会从你发送过来的cookies中拿出这个session id,然后根据这个session id到相应的内存中取你之前存放的数据。 但是,如果你退出登陆了,服务器会清掉属于你的内存区域,所以你再登的话,会产生一个新的session了。

jsession是什么:jsessionid是session的标识。这就好比每个人都有身份证一样。

jsessionid解释的解释如下:

这是一个保险措施 因为Session默认是需要Cookie支持的,但有些客户浏览器是关闭Cookie的【而jsessionid是存储在Cookie中的,如果禁用Cookie的话,也就是说服务器那边得不到jsessionid,这样也就没法根据jsessionid获得对应的session了,获得不了session就得不到session中存储的数据了。】 这个时候就需要在URL中指定服务器上的session标识,也就是类似于“jsessionid=5F4771183629C9834F8382E23BE13C4C” 这种格式。 用一个方法(忘了方法的名字)处理URL串就可以得到这个东西,这个方法会判断你的浏览器是否开启了Cookie,如果他认为应该加他就会加上去。

1.是不是只要一打开一个页面就会产生一个jsessionid? 答:显然不是的。session是有一定作用域的,而且是有时间限制的。

2.在不关闭浏览器的情况下,什么时候jsessionid会改变?我登陆后,登陆然后退出,jsessionid会有什么变化? 答:jsessionid是服务器那边生成的,因为cookie是服务器那边送到客户端的信息。不管能不能修改jsessionid,都不应该修改,如果你修改了,这就失去了jessionid的自身意义了,你修改的话,你让服务器那边如何找到对应的session?找不到的话,你存放在那个session中的数据不是取不到了吗? 登陆然后退出,我认为会重新生成一个jsessionid。因为退出的话,application作用域的数据都会丢失,更何况这个比它作用域还小的session?既然session都消失了,这个jsessionid有什么用?

3.jsessionid是session的标识。这就好比每个人都有身份证一样。

cookie和session机制区别与联系 具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

cookie机制。正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。 cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式 session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。 当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。 保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。 经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器

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.