Javascript标准参考教程(alpha)

基础

数据类型

JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值。)

WARNING

数值( number ):整数和小数(比如1和3.14)

字符串( string ):文本(比如Hello World)。

布尔值( boolean ):表示真伪的两个特殊值,即true(真)和false(假)

undefined :表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值

null :表示空值,即此处的值为空。

对象( object ):各种值组成的集合。

nodeType属性

nodeType属性返回节点类型。如果节点是一个元素节点,nodeType 属性返回 -1。如果节点是属性节点, nodeType 属性返回 2。

节点类型描述
1 (Element)一个元素
2 (Attr)一个属性
3 (Text)一个元素的文本内容 或属性
4 (CDATASection)一个文档的CDATA部分(文本将 不会被解析器解析)
5 (Entity参考手册)实体引用
6 (Entity)一个实体
7 (ProcessingInstruction)一个处理指令
8 (Comment)一个注释
9 (Document)整个文档(DOM树的根节点)
10 (DocumentType)为文档实体提供接口
11 (DocumentFragment)表示邻接节点和它们的子树。
12 (Notation)代表一个符号在DTD中的声明

函数

构造函数

  • 优点:

1、通过构造函数为实例对象定义属性很方便。

  • 缺点:

1、同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

解决办法:JavaScript 的原型对象(prototype)。JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

JS关于辅助函数inherit()的小问题

// 返回一个继承自原型对象proto的属性的新对象
// 这里可以用到ES5的Object.create()函数
function inherit(proto) {
    //proto是一个对象,但不能是null
    if(proto == null) throw TypeError();
    if(Object.create) return Object.create(proto);    //如果Object.create()存在,使用它
    var t = typeof proto;                    //否则进一步检查
    if(t!=='object' && t!=='function') throw TypeError();
    var F = function() {};        // 定义一个空构造函数
    F.prototype = proto;        // 将其原型属性设置为proto
    return new F();                // 使用F()创建proto的继承对象
}

很明显辅助函数的用途是创建一个继承父类原型的新对象

  • 问题

在关于下面的这句判断时,一时无法理解

var t = typeof proto;                    //否则进一步检查
if(t!=='object' && t!=='function') throw TypeError();

我们的印象中原型对象应该是个Object或者直接是字面量,那么传递的参数类型会有“function”类型情况吗

// 测试传递function类型
var func = function() {};
func.text = 'good work';
func.getText = function() {
    return func.text;
};
console.log(typeof func);        // 'function'
// 传递function类型,返回以func为原型的新对象
var subFunc = inherit(func);            
console.log(subFunc.getText());    // 输出:'good work'

好吧,一个证明说明。原来是可以传递'function'类型的

typeof 运算符

JavaScript 有三种方法,可以确定一个值到底是什么类型。

WARNING

typeof 运算符

instanceof 运算符

Object.prototype.toString 方法

instanceof 运算符和 Object.prototype.toString 方法,将在后文介绍。这里介绍 typeof 运算符。

typeof 运算符可以返回一个值的数据类型。

数值、字符串、布尔值分别返回 numberstringboolean:

typeof 123 // "number"
typeof '123' // "string"
typeof false // "boolean"

函数返回 function:

function f() {}
typeof f
// "function"

undefined 返回 undefined:

typeof undefined
// "undefined"

利用这一点,typeof可以用来检查一个没有声明的变量,而不报错。

v
// ReferenceError: v is not defined

typeof v
// "undefined"

上面代码中,变量 v 没有用 var 命令声明,直接使用就会报错。但是,放在 typeof 后面,就不报错了,而是返回 undefined

实际编程中,这个特点通常用在判断语句。

// 错误的写法
if (v) {
  // ...
}
// ReferenceError: v is not defined

// 正确的写法
if (typeof v === "undefined") {
  // ...
}

对象返回 object

typeof window // "object"
typeof {} // "object"
typeof [] // "object"

上面代码中,空数组([])的类型也是 object,这表示在 JavaScript 内部,数组本质上只是一种特殊的对象。这里顺便提一下,instanceof 运算符可以区分数组和对象:

var o = {};
var a = [];

o instanceof Array // false
a instanceof Array // true

null 返回 object:

typeof null // "object"

null 的类型是 object,这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑 null,只把它当作 object 的一种特殊值。后来 null 独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null 返回 object 就没法改变了。

切换 HTML 布尔属性的新方法 Element.toggleAttribute()

参考:

  1. 切换 HTML 布尔属性的新方法 Element.toggleAttribute()

prototype 对象

1、JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

2、JavaScript 规定,每个函数都有一个 prototype 属性,指向一个对象(就是 JavaScript 的原型对象(prototype))。

function f() {}
typeof f.prototype // "object"

上面代码中,函数 f 默认具有 prototype 属性,指向一个对象。

3、对于普通函数来说,prototype 属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

上面代码中,构造函数 Animalprototype 属性,就是实例对象 cat1cat2 的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。

参考:

  1. 《JavaScript 标准参考教程(alpha)》,by 阮一峰 prototype 对象

原型链

JavaScript 规定,所有对象都有自己的原型对象( prototype )。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”( prototype chain ):对象到原型,再到原型的原型……

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。

参考:

  1. 《JavaScript 标准参考教程(alpha)》,by 阮一峰 prototype 对象 - 原型链

js中__proto__和prototype的区别和关系?

问题描述:

js中__proto__和prototype的区别和关系?

答案:

__proto__ (隐式原型)与 prototype (显式原型)

1.是什么?

  • 显式原型 explicit prototype property:

每一个函数在创建之后都会拥有一个名为 prototype 的属性,这个属性指向函数的原型对象。

Note:通过 Function.prototype.bind 方法构造出来的函数是个例外,它没有 prototype 属性。

  • 隐式原型 implicit prototype link:

JavaScript 中任意对象都有一个内置属性[[ prototype ]],在 ES5 之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过 __proto__ 来访问。ES5 中有了对于这个内置属性标准的 Get 方法 Object.getPrototypeOf()

Note:Object.prototype 这个对象是个例外,它的 __proto__ 值为 null.

  • 二者的关系:

隐式原型指向创建这个对象的函数( constructor )的 prototype.

2. 作用是什么

  • 显式原型的作用:用来实现基于原型的继承与属性的共享。

  • 隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问 obj 这个对象中的 x 属性时,如果在 obj 中找不到,那么就会沿着 __proto__ 依次查找。

3. __proto__的指向

__proto__ 的指向到底如何判断呢?根据ECMA定义 'to the value of its constructor’s "prototype" ' ----指向创建这个对象的函数的显式原型。所以关键的点在于找到创建这个对象的构造函数,接下来就来看一下JS中对象被创建的方式,一眼看过去似乎有三种方式:(1)对象字面量的方式 (2)new 的方式 (3)ES5中的Object.create(), 但是我认为本质上只有一种方式,也就是通过 new 来创建。为什么这么说呢,首先字面量的方式是一种为了开发人员更方便创建对象的一个语法糖,本质就是 var o = new Object(); o.xx = xx;o.yy=yy; 再来看看 Object.create(),这是 ES5 中新增的方法,在这之前这被称为原型式继承.

WARNING

道格拉斯在2006年写了一篇文章,题为 Prototypal Inheritance In JavaScript。在这篇文章中,他介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建新对象,同时还不比因此创建自定义类型,为了达到这个目的,他给出了如下函数:

function object(o){
  function F(){}
  F.prototype = o;
  return new F()
}

所以从实现代码 return new F() 中我们可以看到,这依然是通过 new 来创建的。不同之处在于由 Object.create() 创建出来的对象没有构造函数,看到这里你是不是要问,没有构造函数我怎么知道它的 __proto__ 指向哪里呢,其实这里说它没有构造函数是指在 Object.create() 函数外部我们不能访问到它的构造函数,然而在函数内部实现中是有的,它短暂地存在了那么一会儿。假设我们现在就在函数内部,可以看到对象的构造函数是 F, 现在

//以下是用于验证的伪代码
var f = new F(); 
//于是有
f.__proto__ === F.prototype //true
//又因为
F.prototype === o;//true
//所以
f.__proto__ === o;

因此由 Object.create(o) 创建出来的对象它的隐式原型指向 o。好了,对象的创建方式分析完了,现在你应该能够判断一个对象的 __proto__ 指向谁了。

好吧,还是举一些一眼看过去比较疑惑的例子来巩固一下。

  • 构造函数的显示原型的隐式原型:

内建对象(built-in object):比如 Array()Array.prototype.__proto__ 指向什么? Array.prototype 也是一个对象,对象就是由 Object() 这个构造函数创建的,因此 Array.prototype.__proto__ === Object.prototype //true,或者也可以这么理解,所有的内建对象都是由 Object() 创建而来。

  • 自定义对象:
  1. 默认情况下:
function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true 理由同上
  1. 其他情况:

(1)、Foo继承Bar

function Bar(){}
//这时我们想让Foo继承Bar
Foo.prototype = new Bar()
Foo.prototype.__proto__ === Bar.prototype //true

(2)、Foo不继承

//我们不想让Foo继承谁,但是我们要自己重新定义Foo.prototype
Foo.prototype = {
  a:10,
  b:-10
}
//这种方式就是用了对象字面量的方式来创建一个对象,根据前文所述 
Foo.prototype.__proto__ === Object.prototype

注: 以上两种情况都等于完全重写了 Foo.prototype,所以 Foo.prototype.constructor 也跟着改变了,于是乎 constructor 这个属性和原来的构造函数 Foo() 也就切断了联系。

  • 构造函数的隐式原型

既然是构造函数那么它就是 Function() 的实例,因此也就指向 Function.prototype,比如 Object.__proto__ === Function.prototype

4. instanceof

instanceof 操作符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof 的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的:

//设 L instanceof R 
//通过判断
 L.__proto__.__proto__ ..... === R.prototype ?
//最终返回true or false

也就是沿着 L__proto__ 一直寻找到原型链末端,直到等于 R.prototype 为止。知道了这个也就知道为什么以下这些奇怪的表达式为什么会得到相应的值了.

Function instanceof Object // true 
Object instanceof Function // true 
Function instanceof Function //true
Object instanceof Object // true
Number instanceof Number //false

参考:

  1. js中__proto__和prototype的区别和关系?

ajax

ajax请求成功后新窗口window.open()被拦截的解决方法

解决方法:

1、异步改为同步,即:async:false

2、将新开窗口指向为一个对象,然后修改对象的 url

$('.task').bind('click',function(){
    var w = window.open();
    $.ajax({
        type: 'POST',
        url: '/surveyTask',
        dataType: 'json',
        error: function() {
             w.close();
        },
        success: function(res){
             w.location = res.url;
        }
    });
});

浏览器同源政策及其规避方法

参考:浏览器同源政策及其规避方法

跨域资源共享 CORS 详解

参考:跨域资源共享 CORS 详解

参考:浅谈session,cookie,sessionStorage,localStorage的区别及应用场景

Array 对象

稀疏数组与密集数组

一般来说,JavaScript中的数组是稀疏的。

什么是稀疏呢?稀疏也就是说,数组中的元素之间可以有空隙,因为一个数组其实就是一个键值映射。本文解释了如何创建稀疏数组和不稀疏的数组。

1、稀疏数组

创建一个指定长度的稀疏数组很简单:

当你遍历它时,你会发现,它并没有元素,JavaScript会跳过这些缝隙。

还有一些其他情况会生成稀疏数组,比如:

2、密集数组

1、创建密集数组的技巧:var a = Array.apply(null, Array(3));

//上面的语句其实等同于:
Array(undefined, undefined, undefined)

你现在可以看到数组里面有真实元素了,虽然元素的值是 undefined,但是你可以遍历到这些数组元素了,还可以为每个元素重新赋值:

实际上,JavaScript 并没有常规的数组,所有的数组其实就是个对象,只不过会自动管理一些 "数字"属性 和 `` 罢了。

说的更直接一点,JavaScript 中的数组根本没有索引,因为索引应该是数字,而 JavaScript 中数组的索引其实是字符串:arr[1] 其实就是 arr["1"],给 arr["1000"] = 1arr.length 也会自动变为 1001

这些表现的根本原因就是:JavaScript 中的对象就是字符串到任意值的键值对。注意键只能是字符串。不过目前,ES6 中已经有了类似于 Java 等语言的 Map 类型,键可以是任意类型的值。

参考:javascript中的稀疏数组(sparse array)和密集数组

Array构造的数组使用map为何失效?

示例

假设你需要生成一个从0到99的数组。你要怎么做呢?下面是一种解法:

const arr = [];
for (let i = 0; i < 100; i++) {
  arr[i] = i;
}

看到这种使用传统的 for 循环的方式多多少少会有点不大习惯了。于是乎,我打算采用另一种解法:首选创建一个长度为 100 的空数组,然后由 mapindex 去初始化每一个元素。在 JavaScript 中,你可以用 Array 构造函数来创建数组:

const arr = Array(100).map((_, i) => i);

console.log(arr[0] === undefined);  // true

可是,为什么第一个元素不是 0 而是 undefined

解释

为了理解为何没有生效,我们首先讲清楚一个非常重要的技术点。在内部,JavaScript 数组实际上是对象,对象里面的属性名是数字,对应数组的下标。举个例子:

['a', 'b', 'c']

它实际上等价于:

{
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}

当你去访问数组的第0个元素的时候,实际上访问的是对象中属性名为 0 的元素。

当你使用 Array 构造函数来新建一个数组,那么它会创建一个新的数组对象,并且将长度 length 设定为指定的值。但是,对象里面没有数组索引:

{
  //no index keys!
  length: 100
}

当你去访问数组的第 0 个元素的时候,返回值为 undefined。但并不是指第 0 个元素得值为 undefined,而是当数组下标不存在的时候,默认的返回值。

并且如果数组下标不存在的话,其实 map 函数并没有真正的对每一个元素执行操作。因为只有当下标存在的时候,map 的回调函数才会执行。

解法

因此,我们只需要在数组对象中构造出数组的下标就可以了。最好的方法就是用展开运算符:

const arr = [...Array(100)].map((_, i) => i);
console.log(arr[0]);

使用展开运算符后的数组对象:

{
  0: undefined,
  1: undefined,
  2: undefined,
  ...
  99: undefined,
  length: 100
}

就可以顺利使用map函数了。

实现一个从0到99的数组其他方法

// 方法:一:fill() 方法用于将一个固定值替换数组的元素。
(new Array(100)).fill(0).map((_, i) => i)

// 方法二:keys() 方法返回一个新的Array迭代器,它包含数组中每个索引的键。
[...Array(100).keys()]

参考:Array构造的数组使用map为何失效?

判断一个变量是不是数组

1、Array 对象静态方法:Array.isArray()

var arr = [1, 2, 3];

typeof arr // "object"
Array.isArray(arr) 

2、Object.prototype.toString

// Object、Array、String、Number、Boolean、Function
const  typeOf = (obj) => {
  return toString.call(obj).slice(8, -1);
}

事件

参考:关于事件的前端总结

Object.defineProperty方法

对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。

定义对象可以使用构造函数或字面量的形式:

var obj = new Object;  //obj = {}
obj.name = "张三";  //添加描述
obj.say = function(){};  //添加行为

除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性或修改原有的属性。vue.js的双向数据绑定就是通过Object.defineProperty方法实现的,俗称属性拦截器。

语法

/**
 * obj:必需。目标对象 
 * prop:必需。需定义或修改的属性的名字
 * descriptor:必需。目标属性所拥有的特性
 **/
Object.defineProperty(obj, prop, descriptor)

针对属性,我们可以给这个属性设置一些特性,比如是否只读不可以写;是否可以被for..in或Object.keys()遍历。

给对象的属性添加特性描述,目前提供两种形式:数据描述和存取器描述。

数据描述

当修改或定义对象的某个属性的时候,给这个属性添加一些特性:

var obj = {
    test:"hello"
}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
    configurable:true | false,
    enumerable:true | false,
    value:任意类型的值,
    writable:true | false
});
//对象新添加的属性的特性描述
Object.defineProperty(obj,"newKey",{
    configurable:true | false,
    enumerable:true | false,
    value:任意类型的值,
    writable:true | false
});

数据描述中的属性都是可选的,来看一下设置每一个属性的作用。

  • value

属性对应的值,可以使任意类型的值,默认为undefined

var obj = {}
//第一种情况:不设置value属性
Object.defineProperty(obj, "newKey", {

});
console.log(obj.newKey );  //undefined
------------------------------
//第二种情况:设置value属性
Object.defineProperty(obj,"newKey",{
    value:"hello"
});
console.log(obj.newKey);  //hello
  • writable

属性的值是否可以被重写。设置为true可以被重写;设置为false,不能被重写。默认为false

var obj = {}
//第一种情况:writable设置为false,不能重写。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false
});
//更改newKey的值
obj.newKey = "change value";
console.log( obj.newKey );  //hello

//第二种情况:writable设置为true,可以重写
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true
});
//更改newKey的值
obj.newKey = "change value";
console.log(obj.newKey );  //change value
  • enumerable

此属性是否可以被枚举(使用for...inObject.keys())。设置为true可以被枚举;设置为false,不能被枚举。默认为false

var obj = {}
//第一种情况:enumerable设置为false,不能被枚举。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false
});

//枚举对象的属性
for( var attr in obj ){
    console.log( attr );  
}
//第二种情况:enumerable设置为true,可以被枚举。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:true
});

//枚举对象的属性
for( var attr in obj ){
    console.log( attr );  //newKey
}
  • configurable

是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false

这个属性起到两个作用:

1.目标属性是否可以使用delete删除

2.目标属性是否可以再次设置特性

var obj = {}
//第一种情况:configurable设置为false,不能被删除。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:false
});
//删除属性
delete obj.newKey;
console.log( obj.newKey ); //hello

//第二种情况:configurable设置为true,可以被删除。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:true
});
//删除属性
delete obj.newKey;
console.log( obj.newKey ); //undefined

//-----------------测试是否可以再次修改特性------------------------
var obj = {}
//第一种情况:configurable设置为false,不能再次修改特性。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:false
});

//重新修改特性
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true,
    enumerable:true,
    configurable:true
});
console.log( obj.newKey ); //报错:Uncaught TypeError: Cannot redefine property: newKey

//第二种情况:configurable设置为true,可以再次修改特性。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:true
});

//重新修改特性
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true,
    enumerable:true,
    configurable:true
});
console.log( obj.newKey ); //hello

除了可以给新定义的属性设置特性,也可以给已有的属性设置特性

//定义对象的时候添加的属性,是可删除、可重写、可枚举的。
var obj = {
    test:"hello"
}

//改写值
obj.test = 'change value';

console.log( obj.test ); //'change value'

Object.defineProperty(obj,"test",{
    writable:false
})


//再次改写值
obj.test = 'change value again';

console.log( obj.test ); //依然是:'change value'

提示:一旦使用Object.defineProperty给对象添加属性,那么如果不设置属性的特性,那么configurableenumerablewritable这些值都为默认的false

var obj = {};
//定义的新属性后,这个属性的特性中configurable,enumerable,writable都为默认的值false
//这就导致了neykey这个是不能重写、不能枚举、不能再次设置特性
//
Object.defineProperty(obj, 'newKey',{

});

//设置值
obj.newKey = 'hello';
console.log(obj.newKey);  //undefined

//枚举
for( var attr in obj ){
    console.log(attr);
}

设置的特性总结:

1、value: 设置属性的值

2、writable: 值是否可以重写。true | false

3、enumerable: 目标属性是否可以被枚举。true | false

4、configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false

存取器描述

当使用存取器描述属性的特性的时候,允许设置以下特性属性:

var obj = {};
Object.defineProperty(obj,"newKey",{
    get:function (){} | undefined,
    set:function (value){} | undefined
    configurable: true | false
    enumerable: true | false
});

注意:当使用了gettersetter方法,不允许使用writablevalue这两个属性

  • getter/setter

当设置或获取对象的某个属性的值的时候,可以提供getter/setter方法。

1、getter 是一种获得属性值的方法

2、setter是一种设置属性值的方法。

在特性中使用get/set属性来定义对应的方法。

var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){
        //当获取值的时候触发的函数
        return initValue;    
    },
    set:function (value){
        //当设置值的时候触发的函数,设置的新值通过参数value拿到
        initValue = value;
    }
});
//获取值
console.log( obj.newKey );  //hello

//设置值
obj.newKey = 'change value';

console.log( obj.newKey ); //change value

注意:get或set不是必须成对出现,任写其一就可以。如果不设置方法,则get和set的默认值为undefined

configurable和enumerable同上面的用法。

兼容性

在ie8下只能在DOM对象上使用,尝试在原生的对象使用 Object.defineProperty()会报错

浏览器和Node不同的事件循环(Event Loop)

参考:浏览器和Node不同的事件循环(Event Loop)

mouseover与mouseenter,mouseout和mouseleave

由于mouseenter不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其mouseovermouseout事件,但是却不会触发mouseentermouseleave事件。即mouseenter事件和mouseleave事件只有在指针穿过或离开被选元素时,才会触发mouseenter事件或mouseleave事件,mouseover事件和mouseout事件不论鼠标指针穿过或离开被选元素或其子元素,都会触发mouseover事件和mouseout`事件。

atob和btoa

window.btoa对字符串进行base64编码(注意不能编码中文);

winodw.atobbase64字符串进行解码(对于包含中文的base64编码,不能正确解码);

  • 为什么要编码?

由于一些网络通讯协议的限制, 又或者是出于信息加密的目的, 我们就需要将原信息转换为base64编码,然后才能进行传输.例如,发送某些含有 ASCII 码表中031之间的控制字符的数据。

通常的方法是通过window.btoa()方法对源数据进行编码, 然后接收方使用window.atob()方法对其进行解码, 从而得到原数据。但是这种方法存在的问题是:window.btoa()不支持中文, window.atob()转换含有中文的base64编码的时候中文部分会变为乱码。另一个存在的问题是解码githubreadme数据的时候也是乱码。经过查找相关资料发现了Base64的编码与解码转的最优方案:

// 编码
function utf8_to_b64(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}

// 解码
function b64_to_utf8(str) {
    return decodeURIComponent(escape(window.atob(str)));
}

// Usage:
utf8_to_b64('✓ à la mode'); // 4pyTIMOgIGxhIG1vZGU=
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

utf8_to_b64('I \u2661 Unicode!'); // SSDimaEgVW5pY29kZSE=
b64_to_utf8('SSDimaEgVW5pY29kZSE='); // "I ♡ Unicode!"

utf8_to_b64('我爱中国'); // 5oiR54ix5Lit5Zu9
b64_to_utf8('SSDimaEgVW5pY29kZSE='); // "我爱中国"

utf8_to_b64(123456); // MTIzNDU2
b64_to_utf8("MTIzNDU2"); // 123456

模块化

CommonJS、AMD、CMD、UMD、ES6

  • CommonJS

CommonJS是一个更偏向于服务器端的规范。NodeJS采用了这个规范。CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。

由于CommonJS是同步加载模块,这对于服务器端不是一个问题,因为所有的模块都放在本地硬盘。等待模块时间就是硬盘读取文件时间,很小。但是,对于浏览器而言,它需要从服务器加载模块,涉及到网速,代理等原因,一旦等待时间过长,浏览器处于”假死”状态。所以在浏览器端,不适合于CommonJS规范。所以在浏览器端又出现了一个规范—-AMD

  • AMD

CommonJS解决了模块化的问题,但这种同步加载方式并不适合于浏览器端。AMDAsynchronous Module Definition的缩写,即”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。 这里异步指的是不堵塞浏览器其他任务(dom构建,css渲染等),而加载内部是同步的(加载完模块后立即执行回调)。

AMD也采用require命令加载模块。当require([module], callback)函数加载模块的时候,就会先加载dependenceModule模块。当有多个依赖时,就将所有的依赖都写在define(['dependenceModule'], function(dependenceModule) {})函数第一个参数数组中,所以说AMD是依赖前置的。这不同于CMD规范,它是依赖就近的。

  • CMD

CMD推崇依赖就近,延迟执行。

// cmd
define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  var b = require('./b');
  b.doSomething();
})

// AMD
define(['a', 'b'], function(a, b) {
  a.doSomething();
  b.doSomething();
})
  • UMD

由于CommonJS是服务器端的规范,跟AMDCMD两个标准实际不冲突。

// 兼容不同规范
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之类的
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    // 方法
    function a(){}; // 私有方法,因为它没被返回 (见下面)
    function b(){}; // 公共方法,因为被返回了
    function c(){}; // 公共方法,因为被返回了
    // 暴露公共方法
    return {
        b: b,
        c: c
    }
}));
  • ES6

es6通过importexport实现模块的输入输出。

module.exports,exports,export和export default,import与require区别

CommonJS规范

博文

crypto-js :加密框架

参考:crypto-js

AES加密

  • 安装
npm install crypto-js
  • aes加密: crypto.js
import CryptoJS from "crypto-js";

const key = CryptoJS.enc.Utf8.parse("1234567890000000"); //16位
const iv = CryptoJS.enc.Utf8.parse("1234567890000000");

export default {
  //aes加密
  encrypt(word) {
    let encrypted = "";
    if (typeof word == "string") {
      const srcs = CryptoJS.enc.Utf8.parse(word);
      encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
      });
    } else if (typeof word == "object") {
      //对象格式的转成json字符串
      const data = JSON.stringify(word);
      const srcs = CryptoJS.enc.Utf8.parse(data);
      encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
      });
    }
    return encrypted.ciphertext.toString();
  },
  // aes解密
  decrypt(word) {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(word);
    const srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(srcs, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
  }
};

  • 使用
import Crypto from "@/utils/crypto";

Crypto.encrypt("✓ à la mode"); // b915bf40c4795227488da86978f55fce
Crypto.decrypt(userPwd); // "✓ à la mode"

Crypto.encrypt("✓ à la mode"); // 6317313288b32bf1909f165ec530d60a
Crypto.decrypt(userPwd); // "I ♡ Unicode!"

Crypto.encrypt("我爱中国"); // 1898a34273855f55255437aa22f87504
Crypto.decrypt(userPwd); // "我爱中国"

Crypto.encrypt("123456"); // dd7a6c4edc68e683b700a7a2846a2bc6
Crypto.decrypt(userPwd); // "123456"

正则表达式

()和$1...$9的理解和使用

()就是起到一个分组作用,将匹配到的放到mathches集合中,$相当于集合名字,1-9就相当于索引,$1...$9相当于对应索引的值。

  • 示例一
// 将yyyy-MM-dd格式的日期转换为yyyy年MM月dd日
const str = "2018-07-02";
const reg =/(\d{4})\-(\d{2})\-(\d{2})/;
const date = str.replace(reg,"$1年$2月$3日");
console.log(date); // "2018年07月02日"
  • 示例二
// 将yyyy-MM-dd格式的日期转换为yyyy/MM/dd/
const str = "2018-07-02";
const reg =/(\d{4})\-(\d{2})\-(\d{2})/;
const date = str.replace(reg,"$1/$2/$3");
console.log(date); // "2018/07/02"
上次更新: 2018-7-16 09:26:54