
分享离线打包js的加密方法
最近在做一个app小工具,打算发布在ios和android平台,又不想双平台各写一份代码,就这样mui成为了选项。
顺利做完要发布前,代码的安全性成了考虑。先说下个人的经历,开发技术栈主要集中在iOS app和手游上,前端算是菜鸟,只是稍有入门,js风格没有讲究范式,因此代码写的很随兴,全局变量、全局函数四处飞,写的时候是爽了,要做压缩混淆时就有点傻眼了。看了各种js的压缩教程,也试了各种压缩和混淆工具后,效果都不是太好,全局变量和函数名基本都是明的,也有考试过eval这种混淆方式,虽然混淆后是面目全非了,但是js文件里那个刺眼的eval简直就是在赤裸裸地告诉破解者对应的解密方式。
考虑一番后,打算不走寻常路,对js进行加密(真正意义上的加密,非js混淆),为此我们需要在js载入流程上做改动。
废话不多说,干货开始。
加密后的载入方式如下:
- 在plusReady事件触发时先载入解密js的插件,此插件流程在稍后叙述。
- 利用此插件载入加密过的js。
- 继续执行正常的js逻辑代码。
打包前对敏感的js做加密,再放到包里。
第一点:
需要我们写一个解密的第三方插件,iOS和android各一份。第三方插件的教程在论坛里很清楚,这里各位同学可以自由挑选要使用的加密算法,在native层实现。
第二点:
假定这个包装好的插件入口在plus.jsUtils。
jsUtils.js代码如下:
document.addEventListener( "plusready", function()
{
var pluginName = 'jsUtils', B = window.plus.bridge;
var isBuilder = plus.runtime.version > '8'; //为了确保调试基座也能正常,简单用版本号判断是否调试基座,稳妥做法是用包名判断
var bfUtils;
if (!isBuilder)
jsUtils = {
load : function (files, success, fail) {
var callbackId = B.callbackId(success, fail);
return B.exec(pluginName, "load", [callbackId, files]);
}
};
else
jsUtils = {
load: function (files, success, fail) {
success();
return true;
}
};
window.plus.jsUtils = jsUtils;
}, false );
html载入页面写法:
<script src="js/jsUtils.js"></script>
<script>
mui.init();
document.addEventListener('plusready', function () {
if (plus.jsUtils)
plus.jsUtils(['a.js', 'b.js'], init, function() {});
else
init();
}, false);
function init() {
//正常的代码逻辑
}
</script>
第三方插件执行流程如下:
- 读取加密后的js文件内容。
- 执行解密操作。
- 执行解密后的js代码,回调success函数。在iOS下是[JSFrameContext evaluateJavaScript],android下是IWebview.evalJS (android的evalJS载入是异步,因此要调用带回调参数的的evalJS)。
如此大功告成。
最近在做一个app小工具,打算发布在ios和android平台,又不想双平台各写一份代码,就这样mui成为了选项。
顺利做完要发布前,代码的安全性成了考虑。先说下个人的经历,开发技术栈主要集中在iOS app和手游上,前端算是菜鸟,只是稍有入门,js风格没有讲究范式,因此代码写的很随兴,全局变量、全局函数四处飞,写的时候是爽了,要做压缩混淆时就有点傻眼了。看了各种js的压缩教程,也试了各种压缩和混淆工具后,效果都不是太好,全局变量和函数名基本都是明的,也有考试过eval这种混淆方式,虽然混淆后是面目全非了,但是js文件里那个刺眼的eval简直就是在赤裸裸地告诉破解者对应的解密方式。
考虑一番后,打算不走寻常路,对js进行加密(真正意义上的加密,非js混淆),为此我们需要在js载入流程上做改动。
废话不多说,干货开始。
加密后的载入方式如下:
- 在plusReady事件触发时先载入解密js的插件,此插件流程在稍后叙述。
- 利用此插件载入加密过的js。
- 继续执行正常的js逻辑代码。
打包前对敏感的js做加密,再放到包里。
第一点:
需要我们写一个解密的第三方插件,iOS和android各一份。第三方插件的教程在论坛里很清楚,这里各位同学可以自由挑选要使用的加密算法,在native层实现。
第二点:
假定这个包装好的插件入口在plus.jsUtils。
jsUtils.js代码如下:
document.addEventListener( "plusready", function()
{
var pluginName = 'jsUtils', B = window.plus.bridge;
var isBuilder = plus.runtime.version > '8'; //为了确保调试基座也能正常,简单用版本号判断是否调试基座,稳妥做法是用包名判断
var bfUtils;
if (!isBuilder)
jsUtils = {
load : function (files, success, fail) {
var callbackId = B.callbackId(success, fail);
return B.exec(pluginName, "load", [callbackId, files]);
}
};
else
jsUtils = {
load: function (files, success, fail) {
success();
return true;
}
};
window.plus.jsUtils = jsUtils;
}, false );
html载入页面写法:
<script src="js/jsUtils.js"></script>
<script>
mui.init();
document.addEventListener('plusready', function () {
if (plus.jsUtils)
plus.jsUtils(['a.js', 'b.js'], init, function() {});
else
init();
}, false);
function init() {
//正常的代码逻辑
}
</script>
第三方插件执行流程如下:
- 读取加密后的js文件内容。
- 执行解密操作。
- 执行解密后的js代码,回调success函数。在iOS下是[JSFrameContext evaluateJavaScript],android下是IWebview.evalJS (android的evalJS载入是异步,因此要调用带回调参数的的evalJS)。
如此大功告成。
收起阅读 »
ajax在安卓4.1.1里没用!
用的是官方hello mui
安卓在联想的s890里 系统版本4.1.1
ajax 不管get和post都无效 显示正在请求!
用的是官方hello mui
安卓在联想的s890里 系统版本4.1.1
ajax 不管get和post都无效 显示正在请求!

es6(二)
ES6(第三版)
2.let和const命令
1.let命令
2.块级作用域
3.const命令
4.顶层对象的属性
5.global对象
let只在let所在的代码块内有效.
for循环很适合使用let命令.
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域.
没有变量提升,let所声明的变量一定要在声明后使用,否则报错.
暂时性死区
let不允许在相同作用域内,重复声明同一个变量.
块级作用域的数显,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了.
函数声明语句的行为类似于let,在块级作用域之外不可引用.
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数.如果确实需要,也应该写成函数表示式,而不是
函数声明语句.
//函数表达式
{
let a = 'secret';
let f = function (){
return a;
};
}
//块级作用域写法
{
let tmp = ...;
...
}
const
const声明一个只读的常量,一旦声明,常量的值就不能改变.
将一个对象声明为常量必须非常小心.
冻结,除了将对象本身冻结,对象的属性也应该冻结.下面是一个将对象彻底冻结的函数
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if (typeof obj[Key] === 'object') {
constantize(obj[Key]);
}
})
};
ES6声明变量的6种方法
1.var 2.function 3.let 4.const 5.import 6.class
4.顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象.ES5中,顶层对象的属性与全局变量是等价的.
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的
问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创
造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层
对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗
口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,
全局变量将逐步与顶层对象的属性脱钩。
5.global对象
浏览器里面,顶层对象是window,但Node和web worker没有window;
浏览器和web worker里面,self也指向顶层对象,但是node没有self;
node里面,顶层对象是global,但其他环境都不支持;
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
typeof global === 'object')? global this);
//方法二
var getGlobal = function () {
if (typeof self !=== 'undefined') {return self;}
if (typeof window !== 'undefined') {return window;}
if (typeof global !== 'undefined') {return global;}
throw new Error('unable to locate global object');
}
现在有一个提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。
垫片库system.global模拟了这个提案,可以在所有环境拿到global。
3.变量和解构赋值
1.数组的解构赋值
2.对象的解构赋值
3.字符串的结构赋值
4.数值和布尔值的解构赋值
5.函数参数的解构赋值
6.圆括号的问题
7.用途
1.数组的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring).
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
let [a,b,c] = [1,2,3];
如果解构不成功,变量的值就等于undefined。
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的语句就都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
1.概念
第一次看到这个概念是当年学C++的时候,STL库中的迭代器。在es6中,Iterator也差不多是这个意思。
在es6中,能表示“集合”概念的数据类型大致有四种。
Array,Object,Map,Set
既然是集合,那遍历便是一种基本需求。而Iterator就是为了提供一种统一的接口机制。任何的数据结构,
只要部署了Iterator接口,便可以使用类似的方式完成遍历操作。
https://www.cnblogs.com/toulon/p/6403075.html
对于 Set 结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。
所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else{
x = [1][0];
}
//[1][0]表示数组[1]中的第0个元素[0]
2.对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
3.字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
4.数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
Number.prototype.toString 是什么意思?
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
5.函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x,y]){
return x + y;
}
add([1,2]);
函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y.对于函数内部的代码
来说,他们能感受到的参数就是x和y.
[[1,2],[3,4]].map(([a,b]) => a + b);
map函数需要了解一下
map函数需要了解一下?
Number.prototype.toString 是什么意思?
Array.from() ?????
new Proxy()生成实例,new的底层是什么操作,在函数那一章节????
Object.create(proto) ???????create???
try catch ???
6.圆括号问题
如果模式中出现圆括号怎么处理,es6的规则是,只要有可能导致解构的歧义,就不得使用圆括号.
7.用途
变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将他们放在数组或对象里返回.有了解构赋值,取出这个值就非常方便.
function example() {
return [1,2,3];
}
let [a,b,c] = example();
function example() {
return {
foo: 1,
bar: 2
};
}
let {foo,bar} = example();
6.遍历map结构
const map = new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,value] of map) {
console.log(key + "is" + value);
}
字符串的扩展
1.字符串的Unicode表示法
2.codePointAt()
3.String.fromCodePoint()
4.字符串的遍历器接口
5.at()
6.normalize()
7.includes(),startsWith(),endsWith()
8.repeat();
9.padStart() ,padEnd()
10.matchAll()
11.模板字符串
12.实例:模板编译
13.标签模板
14.String.raw()
15.模板字符串的限制
ES6加强了对Unicode的支持,并且扩展了字符串对象.
\uxxxx中\u代表是unicode的表示法
xxxx表示字符的Unicode码点.
正则的扩展
1.RegExp 构造函数
2.字符串的正则方法
3.u 修饰符
4.y 修饰符
5.sticky 属性
6.flags 属性
7.s 修饰符:dotAll 模式
8.后行断言
9.Unicode 属性类
10.具名组匹配
11.String.prototype.matchAll
var regex = new RegExp('xyz','i');
//等价于
var regex = /xyz/i;
new RegExp(/abc/ig, 'i').flags
// "i"
flags属性为ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符.
/abc/ig.source //返回正则表达式的正文 "abc"
/abc/ig.flags //返回正则表达式的修饰符 "gi"
set和map数据结构
javascript的默认对象表示方式{}可以视为其他语言中的Map和Dictionary的数据结构,即一组键值对.
但是JavaScript的对象有个小问题,就是健必须是字符串.但实际上Number或者其他数据类型作为健也是非常合理的.
为了解决这个问题,最新的ES6规范引入了新的数据类型Map.
var m = new Map();
var s = new Set();
Map 是一组键值对的结构,具有极快的查找速度.
var names = ["a","b","c"];
var sources = [95,75,85];
用Map实现
var m = new Map([['a',95],['b',75],['c',89]]);
m.get('a');//95
var n = new Map();
n.set('admin',89);
n.set('bob',90);
n.has('admin');
Set
set和map类似,也是一组key的集合,但不存储value.由于key不能重复,所以,在set中,没有重复的key
要创建一个set,需要提供一个array作为输入,或者直接创建一个空set
var s1 = new Set();
var s2 = new Set([1,2,3]);
重复元素在set中自动被过滤:
set本身是一个构造函数,用来生成Set数据结构.
const s = new Set();
[2,3,2,4,5,3,7].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
通过add方法向Set加入成员.
另外,两个对象总是不相等的.
let set = new Set();
set.add({});
set.size //1
set.add({});
set.size //2
set实例的属性和方法
set结构的实例有以下属性
Set.prototype.constructor 构造函数,默认就是Set函数
Set.prototype.size 返回Set实例的成员总数.
Set实例的方法分为两大类:操作方法(用于操作数据)和便利方法(用于便利成员).
四个操作方法:
add
delete
has
clear 清除所有成员
看看在判断是否包括一个健上面,Object结构和Set结构的写法不同.
//对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
}
//set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if(properties.has(someName)){
}
Array.from()方法可以将Set结构转为数组.
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
这就提供了去除数组重复成员的另一种方法.
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1,1,2,3]);
8,数组的扩展
function f(x,y,z) {
}
var args = [0,1,2];
f.apply(null,args);
function f(x,y,z) {
}
let args = [0,1,2];
f(...args);
Math.max.apply(null,[14,3,77]);
Math.max(...[14,3,77]);
Math.max(14,3,77);
var arr1 = [0,1,2];
var arr2 = [3,4,5];
Array.prototype.push.apply(arr1,arr2);
arr1.push(...arr2);
new (Date.bind.apply(Date,[null,2015,1,1]))
new Date(...[2015,1,1]);
克隆
const a1 = [1,2];
const a2 = a1.concat(); //a1会返回原数组的克隆
a2[0] = 2;
a1; //[1,2]
const a1 = [1,2];
const a2 = [...a1];
const [...a2] = a1;
上面两种写法a2都是a1的克隆.
(3)与解构赋值结合
Array.from()
Array.from()方法用于将两类对象转为真正的数组:
类似数组的对象(array-like-object) 和 可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length:3
}
var arr1 = [].slice.call(arrayLike);
var arr2 = Array.from(arrayLike);
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
function foo() {
var args = Array.from(arguments);
}
如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组.
扩展运算符(...)也可以将某些数据结构转为数组
const args = [...arguments];
[...document.querySelectorAll('div')];
所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,
任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
Array.of方法用于将一组值,转换为数组。????与push有区别吗?
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不
少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of方法可以用下面的代码模拟实现。
function ArrayOf() {
return [].slice.call(arguments);
}
数组实例的copyWithin()
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置
(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数:
-target(必须):从该位置开始替换数据。如果为负值,表示倒数。
-start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
-end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1,2,3,4,5].copyWithin(0,3) //[4,5,3,4,5]
//将3号位复制到0号位
[1,2,3,4,5].copyWithin(0,3,4) //[4,2,3,4,5]
数组的实例 entries(),keys()和values()用于遍历数组,区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
上面代码表示将从3号位置到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2.
copyWithin用来复制复制复制复制复制
遍历操作
Set结构的实例有四个遍历方法,可以用于遍历成员.
keys 返回键名的遍历器
values 返回健值的遍历器
entries 返回健值对的遍历器
forEach 使用回调函数遍历每个成员
let set = new Set(['red','green','blue']);
for(let item of set.keys()){
console.log(item)
}
for (let item of set.values()) {
console.log(item);
}
for (let item of set.entries()) {
console.log(item);
}
Set结构的实例默认和遍历,他的默认遍历器生成函数就是它的values方法
Set.prototype[Symbol.iterator] === Set.prototype.values //true
这意味着,可以省略values方法,直接用for...of循环遍历set
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
(2)forEach()
遍历的应用
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
数组的map和fillter方法也可以间接用于set了
let set = new Set([1,2,3]);
set = new Set([...set].map(x => x * 2));//{2,4,6}
.map方法返回一个新数组,数组中的元素为原始调用函数处理后的值,按照原始数组顺序依次处理元素.map不会对空
数组进行检测.map()不会改变原始数组.
let set = new Set([1,2,3,4,5]);
set = new Set([...set].filter(x => (x % 2) == 0));
因此使用Set可以很容易地实现并集(union),交集(intersect)和差集(Difference).
let ab = new Set([1,2,3]);
let ba = new Set([4,3,2]);
并集
let union = new Set([...a,...b]);
交集
let intersect = new Set([...ab].filter(x => ba.has(x))); //2
差集
let differences = new Set([...ab].filter(x => !ba.has(x)));
3.Map含义和基本用法
js的对象(Object),本质上是键值对的集合(Hash结构),但传统上只能用字符串当做健,这个他的使用带来了很大的限制.
const m = new Map();
const o = {p: 'Hello World'};
m.set(o,'content');
m.get(o);
m.has(o) //true
m.delete(o) //true
m.has(o) //false
const map = new Map([
['name','张三'],
['title','Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name','张三'],
['title','Author']
];
const map = new Map();
items.forEach(
([key,value]) => map.set(key,value)
);
事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构
(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
Proxy()代理
proxy可以理解成,在目标对象之前架设一层'拦截',外界对该对象的访问,都必须先通过这层拦截,因此提供了
一种机制,可以对外界的访问进行过滤和改写 Proxy这个词的愿意是代理,用在这里表示由他来'代理'这些操作,
可以译为"代理器".
var obj = new Proxy({},{
get: function (target,key,receiver) {
console.log(getting ${key}!
);
return Reflect.get(target,key,receiver);
},
set: function (target,key,value,receiver) {
console.log(setting ${key}!
);
return Reflect.set(target,key,value,receiver);
}
});
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为.
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
es6原生proxy构造函数,用来生成Proxy实例.
var proxy = new Proxy(target,handler);
proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法.其中,new Proxy()表示生成一个proxy实例,
var proxy = new Proxy({},{
get: function(target,property) {
console.log(target); //{}
console.log(property); //time
return 35;
}
});
proxy.time //35
proxy.name //35
proxy.title //35
上例子中,作为构造函数,Proxy接受两个参数,第一个参数是所要代理的目标对象(上例是一个空对象),
即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于么一个被代理的操作,
需要提供一个对应的处理函数,该函数将拦截对应的操作.比如,上面的代码中,配置对象有一个get方法,用来拦截对
目标对象属性的 访问 请求.get方法的两个参数分别是目标对象和所要访问的属性.可以看到,由于拦截函数总是
返回35,所以访问任何属性都得到35
var target = {};
var handler = {};
var proxy = new Proxy(target,handler);
proxy.a = 'b';
target.a //"b"
上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target.
一个技巧是将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用
var object = {proxy: new Proxy(target,handler)};
Proxy()实例的方法
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function (target,property) {
if (property in target) {
return target[property];
}else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name //"张三"
proxy.age //抛出一个错误
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。
如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
let proto = new Proxy({},{
get(target,propertyKey,receiver) {
console.log('GET' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo //"GET foo"
Reflect
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新的API.Reflect对象的设计目的有这样几个.
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,
从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,
Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,
而Reflect.defineProperty(obj, name, desc)则会返回false。
//老写法
try{
Object.defineProperty(target,property,attributes);
}catch(e){
//TODO handle the exception
}
//新写法
if (Reflect.defineProperty(target,property,attributes)) {
//success
} else{
//failure
}
(3)让Object操作都变成函数行为,某些Object
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],
而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,
就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,
完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target,name,value,receiver) {
//reflect应该类似于reflect.set
var success = Reflect.set(target,name,value,receiver);
if (success) {
log('property' + name + 'on' + target + 'set to' + value);
}
return success;
}
});
Proxy方法拦截target对象的属性赋值行为.它采用Reflect.set方法将值赋值给对象的属性,确保完成原有
的行为,然后再部署额外的功能.
var loggedObj = new Proxy(obj, {
get(target,name) {
console.log('get',target,name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target,name){
console.log('has' + name);
return Reflect.has(target, name);
}
});
上面代码中,每一个Proxy对象的拦截操作(get/delete/has),内部都调用对应的Reflect方法,保证原生行为能够
正常执行.添加的工作,就是将每一个操作输出一行日志.
Function.prototype.apply.call(Math.floor,undefined,[1.75]) //1
//新写法
Reflect.apply(Math.floor,undefined,[1.75]) //1
3.实例:使用proxy实现观察者模式
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(${person.name}, ${person.age}
)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observeable = obj => new Proxy(obj,{set});
function set(target,key,value,receiver) {
const result = Reflect.set(target,key,value,receiver);
queuedObservers.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,
拦截赋值操作。拦截函数set之中,会自动执行所有观察者。
Promise
Promise是一种异步编程的解决方案,比传统的解决方案-----回调函数和事件---更合理和更强大.
所谓Promise,简单说就是一个容器,里面保存着未来才会结果的事件(通常是异步操作)的结果.
Promise对象是一个构造函数
const promise = new Promise(function(resolve,reject) {
if () {
} else{
}
})
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
Promise实例生成以后,可以用then方法分别指定resolvued和rejected状态的回调函数.
promise.then(function(value) {
console.log('resolved.');
},function(error){
});
console.log('Hi!');
// Promise
// Hi!
// resolved
const getJSON = function(url) {
//new出来的promise实例
const promise = new Promise(function(resolve,reject) {
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept","application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json){
console.log("Contents:" + json);
},function(error){
console.error('出错了', error);
});
调用resolve或reject并不会终结Promise的参数函数的执行.所以最好在他们前面加上return来防止意外.
Promise.prototype.then()
then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
//...
});
上面的代码使用then方法,一次指定了两个回调函数.第一个回调函数完成以后,会将返回结果作为参数,传入
第二个回调函数.
getJSON("/posts.json").then(function(json) {
return json.post;
}).catch(function(error) {
//处理getJSON和前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果
异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误.另外,then方
法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获
Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.是ES2018引入标准的.
promise
.then(result => {...})
.catch(error => {...})
.finally(() => {...});
finally方法的回调函数不接受任何参数,里面的操作与状态无关,不依赖Promise的执行结果.
finally本质上是then方法的特例
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
它的实现也很简单。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
从上面的实现还可以看到,finally方法总是会返回原来的值。
promise.all用于将多个Pormise实例包装成一个新的Promise实例
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
二维数组,从里面跳出来,就是 arr[3][2]代表数组第三个元素中的数组的第二个元素
Iterator(遍历器)的概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),
ES6 又添加了Map和Set。这样就有了 四种数据集合 ,用户还可以组合使用它们,定义
自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的
接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统
一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处
理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二
是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of循环,Iterator 接口主要供for...of消费。
(1)创建一个指针对象,指向当前数据结构的起始位置.也就是说,遍历器对象本质上,就是一个指针对象
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。
当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
原生具备 Iterator 接口的数据结构如下。
array
map
set
string
typedArray
函数的arguments对象
NodeList对象
//重要 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo();
//上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。
从上面代码还可以看到,参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。
另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。
function foo(optional = undefined) { ··· }
2.rest参数
ES6(第三版)
2.let和const命令
1.let命令
2.块级作用域
3.const命令
4.顶层对象的属性
5.global对象
let只在let所在的代码块内有效.
for循环很适合使用let命令.
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域.
没有变量提升,let所声明的变量一定要在声明后使用,否则报错.
暂时性死区
let不允许在相同作用域内,重复声明同一个变量.
块级作用域的数显,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了.
函数声明语句的行为类似于let,在块级作用域之外不可引用.
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数.如果确实需要,也应该写成函数表示式,而不是
函数声明语句.
//函数表达式
{
let a = 'secret';
let f = function (){
return a;
};
}
//块级作用域写法
{
let tmp = ...;
...
}
const
const声明一个只读的常量,一旦声明,常量的值就不能改变.
将一个对象声明为常量必须非常小心.
冻结,除了将对象本身冻结,对象的属性也应该冻结.下面是一个将对象彻底冻结的函数
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if (typeof obj[Key] === 'object') {
constantize(obj[Key]);
}
})
};
ES6声明变量的6种方法
1.var 2.function 3.let 4.const 5.import 6.class
4.顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象.ES5中,顶层对象的属性与全局变量是等价的.
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的
问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创
造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层
对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗
口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;
另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,
全局变量将逐步与顶层对象的属性脱钩。
5.global对象
浏览器里面,顶层对象是window,但Node和web worker没有window;
浏览器和web worker里面,self也指向顶层对象,但是node没有self;
node里面,顶层对象是global,但其他环境都不支持;
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全政策),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
typeof global === 'object')? global this);
//方法二
var getGlobal = function () {
if (typeof self !=== 'undefined') {return self;}
if (typeof window !== 'undefined') {return window;}
if (typeof global !== 'undefined') {return global;}
throw new Error('unable to locate global object');
}
现在有一个提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象。
垫片库system.global模拟了这个提案,可以在所有环境拿到global。
3.变量和解构赋值
1.数组的解构赋值
2.对象的解构赋值
3.字符串的结构赋值
4.数值和布尔值的解构赋值
5.函数参数的解构赋值
6.圆括号的问题
7.用途
1.数组的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring).
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
let [a,b,c] = [1,2,3];
如果解构不成功,变量的值就等于undefined。
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
上面的语句就都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
1.概念
第一次看到这个概念是当年学C++的时候,STL库中的迭代器。在es6中,Iterator也差不多是这个意思。
在es6中,能表示“集合”概念的数据类型大致有四种。
Array,Object,Map,Set
既然是集合,那遍历便是一种基本需求。而Iterator就是为了提供一种统一的接口机制。任何的数据结构,
只要部署了Iterator接口,便可以使用类似的方式完成遍历操作。
https://www.cnblogs.com/toulon/p/6403075.html
对于 Set 结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
解构赋值允许指定默认值。
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。
所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else{
x = [1][0];
}
//[1][0]表示数组[1]中的第0个元素[0]
2.对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
3.字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
4.数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
Number.prototype.toString 是什么意思?
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
5.函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x,y]){
return x + y;
}
add([1,2]);
函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y.对于函数内部的代码
来说,他们能感受到的参数就是x和y.
[[1,2],[3,4]].map(([a,b]) => a + b);
map函数需要了解一下
map函数需要了解一下?
Number.prototype.toString 是什么意思?
Array.from() ?????
new Proxy()生成实例,new的底层是什么操作,在函数那一章节????
Object.create(proto) ???????create???
try catch ???
6.圆括号问题
如果模式中出现圆括号怎么处理,es6的规则是,只要有可能导致解构的歧义,就不得使用圆括号.
7.用途
变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将他们放在数组或对象里返回.有了解构赋值,取出这个值就非常方便.
function example() {
return [1,2,3];
}
let [a,b,c] = example();
function example() {
return {
foo: 1,
bar: 2
};
}
let {foo,bar} = example();
6.遍历map结构
const map = new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,value] of map) {
console.log(key + "is" + value);
}
字符串的扩展
1.字符串的Unicode表示法
2.codePointAt()
3.String.fromCodePoint()
4.字符串的遍历器接口
5.at()
6.normalize()
7.includes(),startsWith(),endsWith()
8.repeat();
9.padStart() ,padEnd()
10.matchAll()
11.模板字符串
12.实例:模板编译
13.标签模板
14.String.raw()
15.模板字符串的限制
ES6加强了对Unicode的支持,并且扩展了字符串对象.
\uxxxx中\u代表是unicode的表示法
xxxx表示字符的Unicode码点.
正则的扩展
1.RegExp 构造函数
2.字符串的正则方法
3.u 修饰符
4.y 修饰符
5.sticky 属性
6.flags 属性
7.s 修饰符:dotAll 模式
8.后行断言
9.Unicode 属性类
10.具名组匹配
11.String.prototype.matchAll
var regex = new RegExp('xyz','i');
//等价于
var regex = /xyz/i;
new RegExp(/abc/ig, 'i').flags
// "i"
flags属性为ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符.
/abc/ig.source //返回正则表达式的正文 "abc"
/abc/ig.flags //返回正则表达式的修饰符 "gi"
set和map数据结构
javascript的默认对象表示方式{}可以视为其他语言中的Map和Dictionary的数据结构,即一组键值对.
但是JavaScript的对象有个小问题,就是健必须是字符串.但实际上Number或者其他数据类型作为健也是非常合理的.
为了解决这个问题,最新的ES6规范引入了新的数据类型Map.
var m = new Map();
var s = new Set();
Map 是一组键值对的结构,具有极快的查找速度.
var names = ["a","b","c"];
var sources = [95,75,85];
用Map实现
var m = new Map([['a',95],['b',75],['c',89]]);
m.get('a');//95
var n = new Map();
n.set('admin',89);
n.set('bob',90);
n.has('admin');
Set
set和map类似,也是一组key的集合,但不存储value.由于key不能重复,所以,在set中,没有重复的key
要创建一个set,需要提供一个array作为输入,或者直接创建一个空set
var s1 = new Set();
var s2 = new Set([1,2,3]);
重复元素在set中自动被过滤:
set本身是一个构造函数,用来生成Set数据结构.
const s = new Set();
[2,3,2,4,5,3,7].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
通过add方法向Set加入成员.
另外,两个对象总是不相等的.
let set = new Set();
set.add({});
set.size //1
set.add({});
set.size //2
set实例的属性和方法
set结构的实例有以下属性
Set.prototype.constructor 构造函数,默认就是Set函数
Set.prototype.size 返回Set实例的成员总数.
Set实例的方法分为两大类:操作方法(用于操作数据)和便利方法(用于便利成员).
四个操作方法:
add
delete
has
clear 清除所有成员
看看在判断是否包括一个健上面,Object结构和Set结构的写法不同.
//对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
}
//set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if(properties.has(someName)){
}
Array.from()方法可以将Set结构转为数组.
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
这就提供了去除数组重复成员的另一种方法.
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1,1,2,3]);
8,数组的扩展
function f(x,y,z) {
}
var args = [0,1,2];
f.apply(null,args);
function f(x,y,z) {
}
let args = [0,1,2];
f(...args);
Math.max.apply(null,[14,3,77]);
Math.max(...[14,3,77]);
Math.max(14,3,77);
var arr1 = [0,1,2];
var arr2 = [3,4,5];
Array.prototype.push.apply(arr1,arr2);
arr1.push(...arr2);
new (Date.bind.apply(Date,[null,2015,1,1]))
new Date(...[2015,1,1]);
克隆
const a1 = [1,2];
const a2 = a1.concat(); //a1会返回原数组的克隆
a2[0] = 2;
a1; //[1,2]
const a1 = [1,2];
const a2 = [...a1];
const [...a2] = a1;
上面两种写法a2都是a1的克隆.
(3)与解构赋值结合
Array.from()
Array.from()方法用于将两类对象转为真正的数组:
类似数组的对象(array-like-object) 和 可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length:3
}
var arr1 = [].slice.call(arrayLike);
var arr2 = Array.from(arrayLike);
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
function foo() {
var args = Array.from(arguments);
}
如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组.
扩展运算符(...)也可以将某些数据结构转为数组
const args = [...arguments];
[...document.querySelectorAll('div')];
所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,
任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
Array.of方法用于将一组值,转换为数组。????与push有区别吗?
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不
少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of方法可以用下面的代码模拟实现。
function ArrayOf() {
return [].slice.call(arguments);
}
数组实例的copyWithin()
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置
(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数:
-target(必须):从该位置开始替换数据。如果为负值,表示倒数。
-start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
-end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
[1,2,3,4,5].copyWithin(0,3) //[4,5,3,4,5]
//将3号位复制到0号位
[1,2,3,4,5].copyWithin(0,3,4) //[4,2,3,4,5]
数组的实例 entries(),keys()和values()用于遍历数组,区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
上面代码表示将从3号位置到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2.
copyWithin用来复制复制复制复制复制
遍历操作
Set结构的实例有四个遍历方法,可以用于遍历成员.
keys 返回键名的遍历器
values 返回健值的遍历器
entries 返回健值对的遍历器
forEach 使用回调函数遍历每个成员
let set = new Set(['red','green','blue']);
for(let item of set.keys()){
console.log(item)
}
for (let item of set.values()) {
console.log(item);
}
for (let item of set.entries()) {
console.log(item);
}
Set结构的实例默认和遍历,他的默认遍历器生成函数就是它的values方法
Set.prototype[Symbol.iterator] === Set.prototype.values //true
这意味着,可以省略values方法,直接用for...of循环遍历set
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
(2)forEach()
遍历的应用
扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
数组的map和fillter方法也可以间接用于set了
let set = new Set([1,2,3]);
set = new Set([...set].map(x => x * 2));//{2,4,6}
.map方法返回一个新数组,数组中的元素为原始调用函数处理后的值,按照原始数组顺序依次处理元素.map不会对空
数组进行检测.map()不会改变原始数组.
let set = new Set([1,2,3,4,5]);
set = new Set([...set].filter(x => (x % 2) == 0));
因此使用Set可以很容易地实现并集(union),交集(intersect)和差集(Difference).
let ab = new Set([1,2,3]);
let ba = new Set([4,3,2]);
并集
let union = new Set([...a,...b]);
交集
let intersect = new Set([...ab].filter(x => ba.has(x))); //2
差集
let differences = new Set([...ab].filter(x => !ba.has(x)));
3.Map含义和基本用法
js的对象(Object),本质上是键值对的集合(Hash结构),但传统上只能用字符串当做健,这个他的使用带来了很大的限制.
const m = new Map();
const o = {p: 'Hello World'};
m.set(o,'content');
m.get(o);
m.has(o) //true
m.delete(o) //true
m.has(o) //false
const map = new Map([
['name','张三'],
['title','Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name','张三'],
['title','Author']
];
const map = new Map();
items.forEach(
([key,value]) => map.set(key,value)
);
事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构
(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
Proxy()代理
proxy可以理解成,在目标对象之前架设一层'拦截',外界对该对象的访问,都必须先通过这层拦截,因此提供了
一种机制,可以对外界的访问进行过滤和改写 Proxy这个词的愿意是代理,用在这里表示由他来'代理'这些操作,
可以译为"代理器".
var obj = new Proxy({},{
get: function (target,key,receiver) {
console.log(getting ${key}!
);
return Reflect.get(target,key,receiver);
},
set: function (target,key,value,receiver) {
console.log(setting ${key}!
);
return Reflect.set(target,key,value,receiver);
}
});
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为.
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
es6原生proxy构造函数,用来生成Proxy实例.
var proxy = new Proxy(target,handler);
proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法.其中,new Proxy()表示生成一个proxy实例,
var proxy = new Proxy({},{
get: function(target,property) {
console.log(target); //{}
console.log(property); //time
return 35;
}
});
proxy.time //35
proxy.name //35
proxy.title //35
上例子中,作为构造函数,Proxy接受两个参数,第一个参数是所要代理的目标对象(上例是一个空对象),
即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于么一个被代理的操作,
需要提供一个对应的处理函数,该函数将拦截对应的操作.比如,上面的代码中,配置对象有一个get方法,用来拦截对
目标对象属性的 访问 请求.get方法的两个参数分别是目标对象和所要访问的属性.可以看到,由于拦截函数总是
返回35,所以访问任何属性都得到35
var target = {};
var handler = {};
var proxy = new Proxy(target,handler);
proxy.a = 'b';
target.a //"b"
上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target.
一个技巧是将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用
var object = {proxy: new Proxy(target,handler)};
Proxy()实例的方法
var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function (target,property) {
if (property in target) {
return target[property];
}else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.name //"张三"
proxy.age //抛出一个错误
上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。
如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
let proto = new Proxy({},{
get(target,propertyKey,receiver) {
console.log('GET' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo //"GET foo"
Reflect
Reflect对象与Proxy对象一样,也是ES6为了操作对象而提供的新的API.Reflect对象的设计目的有这样几个.
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,
从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,
Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,
而Reflect.defineProperty(obj, name, desc)则会返回false。
//老写法
try{
Object.defineProperty(target,property,attributes);
}catch(e){
//TODO handle the exception
}
//新写法
if (Reflect.defineProperty(target,property,attributes)) {
//success
} else{
//failure
}
(3)让Object操作都变成函数行为,某些Object
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],
而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,
就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,
完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target,name,value,receiver) {
//reflect应该类似于reflect.set
var success = Reflect.set(target,name,value,receiver);
if (success) {
log('property' + name + 'on' + target + 'set to' + value);
}
return success;
}
});
Proxy方法拦截target对象的属性赋值行为.它采用Reflect.set方法将值赋值给对象的属性,确保完成原有
的行为,然后再部署额外的功能.
var loggedObj = new Proxy(obj, {
get(target,name) {
console.log('get',target,name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target,name){
console.log('has' + name);
return Reflect.has(target, name);
}
});
上面代码中,每一个Proxy对象的拦截操作(get/delete/has),内部都调用对应的Reflect方法,保证原生行为能够
正常执行.添加的工作,就是将每一个操作输出一行日志.
Function.prototype.apply.call(Math.floor,undefined,[1.75]) //1
//新写法
Reflect.apply(Math.floor,undefined,[1.75]) //1
3.实例:使用proxy实现观察者模式
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(${person.name}, ${person.age}
)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observeable = obj => new Proxy(obj,{set});
function set(target,key,value,receiver) {
const result = Reflect.set(target,key,value,receiver);
queuedObservers.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,
拦截赋值操作。拦截函数set之中,会自动执行所有观察者。
Promise
Promise是一种异步编程的解决方案,比传统的解决方案-----回调函数和事件---更合理和更强大.
所谓Promise,简单说就是一个容器,里面保存着未来才会结果的事件(通常是异步操作)的结果.
Promise对象是一个构造函数
const promise = new Promise(function(resolve,reject) {
if () {
} else{
}
})
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
Promise实例生成以后,可以用then方法分别指定resolvued和rejected状态的回调函数.
promise.then(function(value) {
console.log('resolved.');
},function(error){
});
console.log('Hi!');
// Promise
// Hi!
// resolved
const getJSON = function(url) {
//new出来的promise实例
const promise = new Promise(function(resolve,reject) {
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept","application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json){
console.log("Contents:" + json);
},function(error){
console.error('出错了', error);
});
调用resolve或reject并不会终结Promise的参数函数的执行.所以最好在他们前面加上return来防止意外.
Promise.prototype.then()
then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
//...
});
上面的代码使用then方法,一次指定了两个回调函数.第一个回调函数完成以后,会将返回结果作为参数,传入
第二个回调函数.
getJSON("/posts.json").then(function(json) {
return json.post;
}).catch(function(error) {
//处理getJSON和前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果
异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误.另外,then方
法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获
Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.是ES2018引入标准的.
promise
.then(result => {...})
.catch(error => {...})
.finally(() => {...});
finally方法的回调函数不接受任何参数,里面的操作与状态无关,不依赖Promise的执行结果.
finally本质上是then方法的特例
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
上面代码中,如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。有了finally方法,则只需要写一次。
它的实现也很简单。
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
从上面的实现还可以看到,finally方法总是会返回原来的值。
promise.all用于将多个Pormise实例包装成一个新的Promise实例
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
二维数组,从里面跳出来,就是 arr[3][2]代表数组第三个元素中的数组的第二个元素
Iterator(遍历器)的概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),
ES6 又添加了Map和Set。这样就有了 四种数据集合 ,用户还可以组合使用它们,定义
自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的
接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统
一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处
理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二
是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of循环,Iterator 接口主要供for...of消费。
(1)创建一个指针对象,指向当前数据结构的起始位置.也就是说,遍历器对象本质上,就是一个指针对象
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。
当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
原生具备 Iterator 接口的数据结构如下。
array
map
set
string
typedArray
函数的arguments对象
NodeList对象
//重要 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo();
//上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。
从上面代码还可以看到,参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。
另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。
function foo(optional = undefined) { ··· }
2.rest参数
收起阅读 »

lodash
一、什么是lodash?
lodash库是一个具有一致接口、模块化、高性能等特性的JavaScript工具库。
lodash是一个JavaScript库,也是NodeJS的常用模块,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript尚未制定的规范,但同时被业界认可的辅助函数。
二、lodash模块组成
Array,适用于数组类型,比如填充数据、查找元素、数组分片定操作;
Collection,适用于数组和对象类型,部分适用于字符串,比如分组、查找、过滤等操作;
Function,适用于函数类型,常用于执行类型判断和类型转换;
Lang,普遍适用于各种类型,常用于执行类型判断和类型转化;
Math,适用于数值类型,常用于执行数学运算;
Number,适用于生成随机数,比较数值与数值区间的关系;
Object,适用于对象类型,常用于对象的创建、扩展、类型转换、检索、集合等操作;
Seq,常用于创建链式调用,提高执行性能(惰性计算);
String,适用于字符串类型;
三、安装及使用
以 _.groupBy()方法为例来讲:
(一)使用方法
1.安装命令: npm i --save lodash
2.使用方法;
import _ from ‘lodsh’;
let names = require('./names');
names = _.groupBy(require('./names'),(name) => name[0].toUpperCase( ));
(二)参数详细介绍
.groupBy(collection,[iteratee = .identity])

例子
五、举个“栗”子
我们要实现分组的城市列表,类似于微信的通信录列表,上张图:
假设我们现在只有这样的数据:
那怎么实现呢?用groupBy
import _ from 'lodash';
let cities = reuqire('./beforeCity.json');
...
getCityInfo( ){
console.log('cities=',cities);
let cityList = [ ];
cityList = _.groupBy(cities, (city) => city.pinyin[0]);
console.log('cityList=',cityList);
}
分组结果如下:
这样分组之后就很方便了
作者:sybil052
链接:https://www.jianshu.com/p/428b5b2fd913
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Lodash
有多年开发经验的工程师,往往都会有自己的一套工具库,成为utils、helpers等等,这套库一方面是自己的技术积累,另一方面也是对某项技术的扩展,领先于技术规范的制定的实现。
Lodash 就是这样的一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前 ECMAScript 尚未制定的规范,但同时被业界所认可的辅助函数。目前每天使用 npm 安装 Lodash 的数量在百万级以上,这在一定程度上证明了其代码的健壮性,值得我们在项目中一试。
模块组成
Lodash 提供的辅助函数主要分为以下几类:
Array,适用于数组类型,比如填充数据、查找元素、数组分片等操作
Collection,适用于数组和对象类型,部分适用于字符串,比如分组、查找、过滤等操作
Function,适用于函数类型,比如节流、延迟、缓存、设置钩子等操作
Lang,普遍适用于各种类型,常用于执行类型判断和类型转换
Math,适用于数值类型,常用于执行数学运算
Number,适用于生成随机数,比较数值与数值区间的关系
Object,适用于对象类型,常用于对象的创建、扩展、类型转换、检索、集合等操作
Seq,常用于创建链式调用,提高执行性能(惰性计算)
String,适用于字符串类型
lodash/fp 模块提供了更接近函数式编程的开发方式,其内部的函数经过包装,具有 immutable、auto-curried、iteratee-first、data-last(官方介绍)等特点。Lodash 在 GitHub Wiki 中对 lodash/fp 的特点做了如下概述:
Fixed Arity,固化参数个数,便于柯里化
Rearragned Arguments,重新调整参数位置,便于函数之间的聚合
Capped Iteratee Argument,封装 Iteratee 参数
New Methods
In functional programming, an iteratee is a composable abstraction for incrementally processing sequentially presented chunks of
input data in a purely functional fashion. With iteratees, it is possible to lazily transform how a resource will emit data, for example,
by converting each chunk of the input to uppercase as they are retrieved or by limiting the data to only the five first chunks without
loading the whole input data into memory. Iteratees are also responsible for opening and closing resources, providing predictable
resource management. ———— iteratee, wikipedia
// The `lodash/map` iteratee receives three arguments:
// (value, index|key, collection)
_.map(['6', '8', '10'], parseInt);
// → [6, NaN, 2]
// The `lodash/fp/map` iteratee is capped at one argument:
// (value)
fp.map(parseInt)(['6', '8', '10']);
// → [6, 8, 10]
// `lodash/padStart` accepts an optional `chars` param.
_.padStart('a', 3, '-')
// → '--a'
// `lodash/fp/padStart` does not.
fp.padStart(3)('a');
// → ' a'
fp.padCharsStart('-')(3)('a');
// → '--a'
// `lodash/filter` is data-first iteratee-last:
// (collection, iteratee)
var compact = _.partial(_.filter, _, Boolean);
compact(['a', null, 'c']);
// → ['a', 'c']
// `lodash/fp/filter` is iteratee-first data-last:
// (iteratee, collection)
var compact = fp.filter(Boolean);
compact(['a', null, 'c']);
// → ['a', 'c']
在React+Webpack+Babel(ES6)的开发环境中,使用Lodash需要安装插件babel-plugin-lodash并更新Babel配置文件:
npm install --save lodash
npm install --save-dev babel-plugin-lodash
更新Babel的配置文件 .babelrc:
{
"presets": [
"react",
"es2015",
"stage-0"
],
"plugins": [
"lodash"
]
}
使用方式:
import from 'loadsh';
import {add} from 'lodash/fp';
const addOne = add(1);
.map([1,2,3],addOne);
性能
在 Filip Zawada 的文章《How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation》 中提到了 Lodash 提高执行速度的思路,主要有三点:Lazy Evaluation、Pipelining 和 Deferred Execution。下面两张图来自 Filip 的博客:
假设有如上图所示的问题:从若干个球中取出三个面值小于 10 的球。第一步是从所有的球中取出所有面值小于 10 的球,第二步是从上一步的结果取三个球。
上图是另一种解决方案,如果一个球能够通过第一步,那么就继续执行第二步,直至结束然后测试下一个球……当我们取到三个球之后就中断整个循环。Filip 称这是 Lazy Evaluation Algorithm,就个人理解这并不全面,他后续提到的 Pipelining(管道计算),再加上一个中断循环执行的算法应该更符合这里的图示。
此外,使用 Lodash 的链式调用时,只有显示或隐式调用 .value 方法才会对链式调用的整个操作进行取值,这种不在声明时立即求值,而在使用时求值的方式,是 Lazy Evaluation 最大的特点。
九个实例
受益于 Lodash 的普及程度,使用它可以提高多人开发时阅读代码的效率,减少彼此之间的误解(Loss of Consciousness)。在《Lodash: 10 Javascript Utility Functions That You Should Probably Stop Rewriting》一文中,作者列举了多个常用的 Lodash 函数,实例演示了使用 Lodash 的技巧。
- N 次循环
//1.Basic for loop for(var i = 0;i < 5; i++) { //... } //2.Using Array's join and split methods Array.apply(null,Array(5).forEach(function ( ) { //... }); //lodash 一个lodash解决问题 _.tiem(5,function ( ) { //... });
for 语句是执行循环的不二选择,Array.apply 也可以模拟循环,但在上面代码的使用场景下, _.times( )的解决方式更加简洁和易于理解。
2.深层查找属性值//Fetch the name of the first pet from each owner var ownerArr = [{ "owner": "Colin", "pets":[{"name":"dog1"},{"name":"dog2"}] },{ "owner":"John", "pets":[{"name":"dog3"},{"name":"dog4"}] }]; //Array's map method ownerArr.map(function(owner){ return owner.pets[0].name; });
// Lodash
_.map(ownerArr, 'pets[0].name');
_.map方法是对原生map方法的改进,其中使用pet[0].name字符串对嵌套数据取值的方式简化了很多冗余的代码,非常类似使用jQuery选择DOM节点 ul > li > a,对于前端开发者来说有种久违的亲切感。
3.个性化数组
```javascript
//Array‘s map method
Array.apply(null,Array(6)).map(function(item,index){
return "ball_" + index;
});
//lodash
_.times(6,_.uniqueId.bind(null,'ball_'));
//lodash
_.times(6,_.partial(_.uniqueId,'ball_'));
// eg. [ball_0, ball_1, ball_2, ball_3, ball_4, ball_5]
在上面的代码中,我们要创建一个初始值不同、长度为6的数组,其中_.uniqueId方法用于生成独一无二的标识符(递增的数字,在程序运行期间保持独一无二),_partial方法是对bind的封装。
4.深拷贝
var objA = {
"name": "colin"
}
var objB = _.cloneDeep(objA);
objB === objA //false
javaScript没有直接提供深拷贝的函数,但我们可以用其他函数来模拟,比如 JSON.parse(JSON.stringfy(objectToClone)),但这种方法要求对象中属性值不能是函数。Lodash中的_.cloneDeep函数封装了深拷贝的逻辑,用起来更加简洁。
5.随机数
//Native utility method
function getReandomNumber (min,max) {
return Math.floor(Math.random( ) * (max - min + 1)) +min;
}
getRandomNumber(15,20);
//Lodash
_.random(15,20);
Lodash的随机数生成函数更贴近实际开发,ECMAScript的随机数生成函数是底层必备的接口,两者都不可或缺。此外,使用_.random(15,20,true)还可以在15到20之间生成随机的浮点数。
6.对象扩展
//Adding extend function to Object.prototype
Object.prototype.extend = function(obj) {
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
this[i] = obj[i];
}
}
};
var objA = {“name”:"colin","cat":"suzuki"};
var objB = {"name":"james","age":17};
ojbA.extend(objB);
objA; //{"name":"james","age":17,"cat","suzuki"};
//Lodash
_.assign(objA,objB);
_.assign是浅拷贝,和ES6新增的Object.assign函数功能一致(建议优先使用Object.assign).
多层嵌套如果解决,如jquery的$.extend(true,{});
7.筛选属性
//Naive method:Remove an array of keys from object
Object.prototype.remove = function(arr) {
var that = this;
arr.forEach(function(key){
delete(that[key]);
});
};
var objA = {"name":"colin","car":"suzuki","age":17};
//将remove函数挂载在Object原型链上,在此处就可以使用了
objA.remove(['car','age']);
objA; // {"name":"colin"}
//Lodash
objA = _.omit(objA,['car','age']);
//=> {"name": "colin"}
//Lodash
objA = _.omit(objA,['car','age']);
//=> {"name":"colin"}
objA = _.omit(objA,'car');
// => {"name": "colin", "age": 17};
objA = _.omit(objA,_.isNumber);
//_.isNumber是什么函数
// => {"name": "colin"};
大多数情况下,lodash所提供的辅助函数都会比原生的函数更贴近开发需求。在上面的代码中,开发者可以使用数组、字符串以及函数的方式筛选对象的属性,并且最终会返回一个新的对象,中间执行筛选时不会对旧对象产生影响。
//Naive method:Returning a new object with selected properties
Object.prototype.pick = function(arr) {
var _this = this;
var obj = { };
arr.forEach(function(key){
obj[key] = _this[key];
});
return obj;
}
var objA = {"name": "colin", "car": "suzuki", "age": 17};
var objB = objA.pick{['car','age']};
//{"car":"suzuki","age":17}
//Lodash
var objB = _.pick(objA,['car','age']);
//{"car":"suzuki","age":17}
.pick是.omit的相反操作,用于从其他对象中挑选属性生成新的对象。
8.随机元素
var luckDraw = ["Colin","John","James","Lily","Mary"];
function pickRandomPerson(luckyDraw){
var index = Math.floor(Math.random() * (luckyDraw.length - 1));
return luckyDraw[index];
}
pickRandomPerson(luckyDraw);//John
//Lodash
_.sample(luckyDraw,2);//Colin
// Lodash - Getting 2 random item
_.sample(luckyDraw, 2); // ['John','Lily'] //随机挑选两个元素并返回新的数组
_.sample 支持随机挑选多个元素并返回新的数组。
9.针对JSON.parse的错误处理
//using try-catch to handle the JSON.parse error
function parse (str) {
try {
return JSON.parse(str);
}
catch(e){
return false;
}
}
// With Lodash
function parseLodash(str) {
return _.attempt(JSON.parse.bind(null,str));
}
parse('a');
//=>false
parseLodash('a');
// =>Return an error object
parse('{"name":"colin"}');
// => Return {"name":"colin"}
parseLodash('{"name":"colin"}');
// => Return {"name":"colin"}
如果你在使用JSON.parse时没有预置错误处理,那么它很有可能会成为一个定时炸弹,我们不应该默认接收的JSON对象都是有效的。try-catch是最常见的错误处理方式,如果项目中Lodash,那么可以使用_.attmpt替代try-catch的方式,当解析JSION出错时,改方法会返回一个Error对象。
随着 ES6 的普及,Lodash 的功能或多或少会被原生功能所替代,所以使用时还需要进一步甄别,建议优先使用原生函数,有关 ES6 替代 Lodash 的部分,请参考文章《10 Lodash Features You Can Replace with ES6》(中文版《10 个可用 ES6 替代的 Lodash 特性》)。
其中有两处非常值得一看:
//使用箭头函数创建可复用的路径:
const object = { 'a': [ {'b': {'c':3} },4] };
[
obj => obj.a[0].b.c,
obj => obj.a[1]
].map(path => path(object));
//使用箭头函数编写链式调用
const pipe = functions => data => {
return functions.reduce(
(value,func) => func(value),
data
);
};
const pipeline = pipe([
x => x * 2,
x => x / 3,
x => x > 5,
b => !b
]);
pipeline(5);
//true
pipeline(20);
//false
在ES6中,如果一个函数只接受一个形参且函数体是一个return语句,就可以使用箭头函数简化为:
const func = p => v;
//类似于(不完全相同)
const func = function (p) {
return v;
}
当有多重嵌套时,可以简化为:
const func = a => b => c => a+b+c;
func(1)(2)(3);
// => 6
//类似于
const func = function (a){
return function (b) {
return function (c) {
return a + b + c;
}
}
}
const objects = {'a':[{'b':{'c':3}},4]};
// 这种写法存在问题吗??????map出现的结果为什么是undefined
[function (obj){
obj.a[0].b.c
},function(obj){
obj.a[1]
}].map(function (path) {
console.log(path(objects));
path(objects);
});
//使用箭头函数编写链式调用
const pipe = functions => data => {
return functions.reduce(
(value,func) => func(value),
data
);
};
const pipe = function functions(){
return function data() {
return functions.reduce(
function(value,func){
func(value);
}
)
}
}
const pipeline = pipe([
x => x * 2,
x => x /3,
x => x > 5,
b => !b
]);
作者:pinggod
链接:https://www.jianshu.com/p/7436e40ac5d1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章:https://www.zhihu.com/question/36942520
10 个可用 ES6 替代的 Lodash 特性
参考来源:http://www.zcfy.cc/article/10-lodash-features-you-can-replace-with-es6-467.html
Lodash现在是npm上被依赖最多的包,如果你现在使用ES6的话,实际上你可能不再需要它了。
1.Map,Filter,Reduce
这些集合方法使数据转化变得轻而易举。由于普遍地对此特性的支持,我们可以将它与箭头函数组合起来,以助我们使用比 Lodash 的实现更简便的方法来实现。
_.map([1,2,3],function(n){ return n * 3; });
// [3, 6, 9]
_.reduce([1, 2, 3], function(total, n) { return total + n; }, 0);
// 6
_.filter([1, 2, 3], function(n) { return n <= 2; });
// [1, 2]
//变为
[1,2,3].map(n => n * 3);
[1,2,3].reduce((total,n) => total + n);
[1, 2, 3].filter(n => n <= 2);
不止于此,如果我们使用 ES6 的 polyfill,我们也能使用 find、some、every 和 reduceRight
- Head & Tail
解构语法 让我们可以获取一个列表的头(head)和尾(tail),而无需工具函数。_.head([1, 2, 3]); // 1 _.tail([1, 2, 3]); // [2, 3] //变为 const [head, ...tail] = [1, 2, 3];
也可以用相似的方式获得列表最后一个元素(last)以及除其之外的元素(initial)
```javascript
_.initial([1, 2, 3]);
// -> [1, 2]
_.last([1, 2, 3]);
// 3
// 变为
const [last, ...initial] = [1, 2, 3].reverse();
如果你讨厌 reverse 会改变数据结构,那你可以在调用 reverse 之前使用延展操作符(spread operator)来复制一个数组。
const xs = [1,2,3];
const [last,...initial] = [...xs].reverse( );
3.Rest & Spread
rest 和 spread函数能让我们定义与调用能接受参数数量不定的函数。es6为这两种操作引入了专用的语法。
var say = _.rest(function (what,names) {
var last = _.last(names);
var initial = _.initial(names);
var finalSeparator = (_.size(names) > 1 ? ', &' : ' ');
return what + ' ' + initial.join(', ') + finalSeparator + _.last(names);
});
//what代表实参‘hello’,names代表实参'fred','barney','pebbles'
say('hello','fred','barney','pebbles');
// "hello fred, barney, & pebbles"
//变为
const say = (what,...names)=> {
const [last,...initial] =names.reverse( );
const finalSeparator = (names.length > 1 ? ', &' : ' ');
return `${what} ${initial.join('. ')} ${finalSeparator} ${last}`;
};
say("hello","fred","barney","pebbles");
// "hello fred, barney, & pebbles"
- Curry
如果没有更高级的语言如TypeScript 和 Flow 的支持,我们不能给函数设置类型签名,这使得函数的柯里化(currying)非常困难。当我们接收一个柯里化的函数时,很难知道已经应用了多少参数以及我们接下来该提供什么参数。通过箭头函数,我们能显示地定义柯里化函数,使得它们对其他程序员来说非常易于理解。function add(a,b) { return a + b; } var curriedAdd = _.curry(add); var add2 = curriedAdd(2); add2(1); //3 //变为 const add = a => b => a + b; //需要再思考一下 const add2 = add(2); add2(1); //3
这些显式的柯里化箭头函数对调试非常重要。
var lodashAdd = _.curry(function(a,b) { return a + b; }); var add3 = lodashAdd(3); console.log(add3.length); //0 console.log(add3); // function wrapper() { // var length = arguments.length, // args = Array(length), // index = length;
// while (index--) {
// args[index] = arguments[index];
// }
// }
//变为
const es6Add = a => b => a + b;
const add3 = es6Add(3);
console.log(add3.length);
//1
console.log(add3);
// function b => a + b
如果我们使用函数式的库如 lodash/fp 和 ramda,那我们也能使用箭头函数来免除对自动柯里化风格的需要。
```javascript
_.map(_.prop('name'))(people);
//变为
people.map(person => person.name);
5.Partial
正如柯里化一样,我们也能使用箭头函数来简化显示化偏函数用法。
var greet = function(greeting, name) {
return greeting + ' ' + name;
};
var sayHelloTo = _.partial(greet,'hello');
sayHelloTo('fred');
// "hello fred"
//变化
const sayHelloTo = name => greet('hello',name);
sayHelloTo('fred');
//"hello fred"
我们也能结合剩余参数与延展操作符来部分地应用可变参数函数
const sayHelloTo = (name, ...args) => greet('hello',name, ...args);
sayHelloTo('fred',1,2,3);
// 'hello fred'
- Operators
Lodash把很多句法操作符重新实现成了函数,所以他们可以被传入集合方法。
在大多数情况下,箭头函数能让它们定义地足够简单精炼,一行足矣。_.eq(3,3); //true _.add(10,1); //11 _.map([1,2,3],function(n) { return _.multiply(n,10); }); // [10,20,30] _.reduce([1,2,3], _.add); //6 //变为 3 === 3 10 + 1 [1,2,3].map(n => n * 10); [1,2,3].reduce((total,n) => total + n);
7.Paths
许多Lodash的函数把路径当做字符串或者数组。然而我们可以使用箭头函数来创建更多可重用的路径。var object = {'a':[{'b':{'c':3}},4]}; _.at(object,['a[0].b.c','a[1]']); // [3,4] _.at(['a','b','c'],0,2); // ['a','c'] //变为 [ obj => obj.a[0].b.c, obj => obj.a[1] ].map(path => path(object)); [ arr => arr[0], arr => arr[2] ].map(path => path(['a','b','c']));
因为这些路径“只是函数”,所以我们也能复合他们。
const getFirstPerson = people => people[0]; const getPostCode = person => person.address.postcode; const getFirstPostCode = prople => getPostCode(getFirstPerson(people)); 我们甚至能创建更高阶能接收参数的路径。 const getFirstNPeople = n => people => people.slice(0,n); const getFirst5People = getFirstNPeople(5); const getFirst5PostCodes = people => getFirst5People(people).map(getPostCode);
8.Pick
pick工具能让我们从一个对象中选择我们想要的属性。我们也能通过解构与对象字面量简写来获取同样的结果。var object = {‘a’:1,'b':2,'c':3}; return _.pick(object,['a','c']); //{a:1,c:3} //变为 const {a,c} = {a:1,b:2,c:3}; return {a,c};
9.Constant,Identity,Noop
Lodash提供了一些工具函数来创建简单的具有某一特定行为的函数。_.constant({'a':1})( ); //{a:1} _.identify({user:'fred'}); //{user:'fred'} _noop( ); //undefined
我们也能使用箭头函数行内定义这些函数。
const constant = x => () => x; const identity = x => x; const noop = () => undefined;
或者我们也能把上面的例子重写为:
(() => ({ a: 1 }))(); // { a: 1 } (x => x)({ user: 'fred' }); // { user: 'fred' } (() => undefined)(); // undefined
10.Chaining &Flow
Lodash 提供了一些函数来帮我们编写链式的语句。在大多情况下,内置的集合函数会返回一个数组实例,能直接被链式调用。但某些情况下,这些方法会改变这个集合,这样就不可能再直接地链式调用了(译者注:需要自己返回实例)。
然而我们也能以一个箭头函数数组来定义同样的转化。_([1, 2, 3]) .tap(function(array) { // 修改输入函数 array.pop(); }) .reverse() .value(); // [2, 1]
// 变为
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
这样的话,我们甚至不需要去思考 tap 和 thru 的不同。把这个归约包装成工具函数能创造出一个非常有用的多功能工具。
```javascript
const pipe = functions => data => {
return functions.reduce(
(value, func) => func(value),
data
);
};
const pipeline = pipe([
x => x * 2,
x => x / 3,
x => x > 5,
b => !b
]);
pipeline(5);
// true
pipeline(20);
// false
总结
Lodash 仍然还是一个非常优秀的库,这篇文章只提供了一个新鲜的观点,JavaScript 的进化版是如何让我们在一些我们之前可能不得不依赖一些工具模块的场景里允许我们直接解决问题的。
不要忽略它(译者注:Lodash),但——下次你需求一个抽象时——思考一下是否一个简单的函数就能办到了!
一、什么是lodash?
lodash库是一个具有一致接口、模块化、高性能等特性的JavaScript工具库。
lodash是一个JavaScript库,也是NodeJS的常用模块,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript尚未制定的规范,但同时被业界认可的辅助函数。
二、lodash模块组成
Array,适用于数组类型,比如填充数据、查找元素、数组分片定操作;
Collection,适用于数组和对象类型,部分适用于字符串,比如分组、查找、过滤等操作;
Function,适用于函数类型,常用于执行类型判断和类型转换;
Lang,普遍适用于各种类型,常用于执行类型判断和类型转化;
Math,适用于数值类型,常用于执行数学运算;
Number,适用于生成随机数,比较数值与数值区间的关系;
Object,适用于对象类型,常用于对象的创建、扩展、类型转换、检索、集合等操作;
Seq,常用于创建链式调用,提高执行性能(惰性计算);
String,适用于字符串类型;
三、安装及使用
以 _.groupBy()方法为例来讲:
(一)使用方法
1.安装命令: npm i --save lodash
2.使用方法;
import _ from ‘lodsh’;
let names = require('./names');
names = _.groupBy(require('./names'),(name) => name[0].toUpperCase( ));
(二)参数详细介绍
.groupBy(collection,[iteratee = .identity])
例子
五、举个“栗”子
我们要实现分组的城市列表,类似于微信的通信录列表,上张图:
假设我们现在只有这样的数据:
那怎么实现呢?用groupBy
import _ from 'lodash';
let cities = reuqire('./beforeCity.json');
...
getCityInfo( ){
console.log('cities=',cities);
let cityList = [ ];
cityList = _.groupBy(cities, (city) => city.pinyin[0]);
console.log('cityList=',cityList);
}
分组结果如下:
这样分组之后就很方便了
作者:sybil052
链接:https://www.jianshu.com/p/428b5b2fd913
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Lodash
有多年开发经验的工程师,往往都会有自己的一套工具库,成为utils、helpers等等,这套库一方面是自己的技术积累,另一方面也是对某项技术的扩展,领先于技术规范的制定的实现。
Lodash 就是这样的一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前 ECMAScript 尚未制定的规范,但同时被业界所认可的辅助函数。目前每天使用 npm 安装 Lodash 的数量在百万级以上,这在一定程度上证明了其代码的健壮性,值得我们在项目中一试。
模块组成
Lodash 提供的辅助函数主要分为以下几类:
Array,适用于数组类型,比如填充数据、查找元素、数组分片等操作
Collection,适用于数组和对象类型,部分适用于字符串,比如分组、查找、过滤等操作
Function,适用于函数类型,比如节流、延迟、缓存、设置钩子等操作
Lang,普遍适用于各种类型,常用于执行类型判断和类型转换
Math,适用于数值类型,常用于执行数学运算
Number,适用于生成随机数,比较数值与数值区间的关系
Object,适用于对象类型,常用于对象的创建、扩展、类型转换、检索、集合等操作
Seq,常用于创建链式调用,提高执行性能(惰性计算)
String,适用于字符串类型
lodash/fp 模块提供了更接近函数式编程的开发方式,其内部的函数经过包装,具有 immutable、auto-curried、iteratee-first、data-last(官方介绍)等特点。Lodash 在 GitHub Wiki 中对 lodash/fp 的特点做了如下概述:
Fixed Arity,固化参数个数,便于柯里化
Rearragned Arguments,重新调整参数位置,便于函数之间的聚合
Capped Iteratee Argument,封装 Iteratee 参数
New Methods
In functional programming, an iteratee is a composable abstraction for incrementally processing sequentially presented chunks of
input data in a purely functional fashion. With iteratees, it is possible to lazily transform how a resource will emit data, for example,
by converting each chunk of the input to uppercase as they are retrieved or by limiting the data to only the five first chunks without
loading the whole input data into memory. Iteratees are also responsible for opening and closing resources, providing predictable
resource management. ———— iteratee, wikipedia
// The `lodash/map` iteratee receives three arguments:
// (value, index|key, collection)
_.map(['6', '8', '10'], parseInt);
// → [6, NaN, 2]
// The `lodash/fp/map` iteratee is capped at one argument:
// (value)
fp.map(parseInt)(['6', '8', '10']);
// → [6, 8, 10]
// `lodash/padStart` accepts an optional `chars` param.
_.padStart('a', 3, '-')
// → '--a'
// `lodash/fp/padStart` does not.
fp.padStart(3)('a');
// → ' a'
fp.padCharsStart('-')(3)('a');
// → '--a'
// `lodash/filter` is data-first iteratee-last:
// (collection, iteratee)
var compact = _.partial(_.filter, _, Boolean);
compact(['a', null, 'c']);
// → ['a', 'c']
// `lodash/fp/filter` is iteratee-first data-last:
// (iteratee, collection)
var compact = fp.filter(Boolean);
compact(['a', null, 'c']);
// → ['a', 'c']
在React+Webpack+Babel(ES6)的开发环境中,使用Lodash需要安装插件babel-plugin-lodash并更新Babel配置文件:
npm install --save lodash
npm install --save-dev babel-plugin-lodash
更新Babel的配置文件 .babelrc:
{
"presets": [
"react",
"es2015",
"stage-0"
],
"plugins": [
"lodash"
]
}
使用方式:
import from 'loadsh';
import {add} from 'lodash/fp';
const addOne = add(1);
.map([1,2,3],addOne);
性能
在 Filip Zawada 的文章《How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation》 中提到了 Lodash 提高执行速度的思路,主要有三点:Lazy Evaluation、Pipelining 和 Deferred Execution。下面两张图来自 Filip 的博客:
假设有如上图所示的问题:从若干个球中取出三个面值小于 10 的球。第一步是从所有的球中取出所有面值小于 10 的球,第二步是从上一步的结果取三个球。
上图是另一种解决方案,如果一个球能够通过第一步,那么就继续执行第二步,直至结束然后测试下一个球……当我们取到三个球之后就中断整个循环。Filip 称这是 Lazy Evaluation Algorithm,就个人理解这并不全面,他后续提到的 Pipelining(管道计算),再加上一个中断循环执行的算法应该更符合这里的图示。
此外,使用 Lodash 的链式调用时,只有显示或隐式调用 .value 方法才会对链式调用的整个操作进行取值,这种不在声明时立即求值,而在使用时求值的方式,是 Lazy Evaluation 最大的特点。
九个实例
受益于 Lodash 的普及程度,使用它可以提高多人开发时阅读代码的效率,减少彼此之间的误解(Loss of Consciousness)。在《Lodash: 10 Javascript Utility Functions That You Should Probably Stop Rewriting》一文中,作者列举了多个常用的 Lodash 函数,实例演示了使用 Lodash 的技巧。
- N 次循环
//1.Basic for loop for(var i = 0;i < 5; i++) { //... } //2.Using Array's join and split methods Array.apply(null,Array(5).forEach(function ( ) { //... }); //lodash 一个lodash解决问题 _.tiem(5,function ( ) { //... });
for 语句是执行循环的不二选择,Array.apply 也可以模拟循环,但在上面代码的使用场景下, _.times( )的解决方式更加简洁和易于理解。
2.深层查找属性值//Fetch the name of the first pet from each owner var ownerArr = [{ "owner": "Colin", "pets":[{"name":"dog1"},{"name":"dog2"}] },{ "owner":"John", "pets":[{"name":"dog3"},{"name":"dog4"}] }]; //Array's map method ownerArr.map(function(owner){ return owner.pets[0].name; });
// Lodash
_.map(ownerArr, 'pets[0].name');
_.map方法是对原生map方法的改进,其中使用pet[0].name字符串对嵌套数据取值的方式简化了很多冗余的代码,非常类似使用jQuery选择DOM节点 ul > li > a,对于前端开发者来说有种久违的亲切感。
3.个性化数组
```javascript
//Array‘s map method
Array.apply(null,Array(6)).map(function(item,index){
return "ball_" + index;
});
//lodash
_.times(6,_.uniqueId.bind(null,'ball_'));
//lodash
_.times(6,_.partial(_.uniqueId,'ball_'));
// eg. [ball_0, ball_1, ball_2, ball_3, ball_4, ball_5]
在上面的代码中,我们要创建一个初始值不同、长度为6的数组,其中_.uniqueId方法用于生成独一无二的标识符(递增的数字,在程序运行期间保持独一无二),_partial方法是对bind的封装。
4.深拷贝
var objA = {
"name": "colin"
}
var objB = _.cloneDeep(objA);
objB === objA //false
javaScript没有直接提供深拷贝的函数,但我们可以用其他函数来模拟,比如 JSON.parse(JSON.stringfy(objectToClone)),但这种方法要求对象中属性值不能是函数。Lodash中的_.cloneDeep函数封装了深拷贝的逻辑,用起来更加简洁。
5.随机数
//Native utility method
function getReandomNumber (min,max) {
return Math.floor(Math.random( ) * (max - min + 1)) +min;
}
getRandomNumber(15,20);
//Lodash
_.random(15,20);
Lodash的随机数生成函数更贴近实际开发,ECMAScript的随机数生成函数是底层必备的接口,两者都不可或缺。此外,使用_.random(15,20,true)还可以在15到20之间生成随机的浮点数。
6.对象扩展
//Adding extend function to Object.prototype
Object.prototype.extend = function(obj) {
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
this[i] = obj[i];
}
}
};
var objA = {“name”:"colin","cat":"suzuki"};
var objB = {"name":"james","age":17};
ojbA.extend(objB);
objA; //{"name":"james","age":17,"cat","suzuki"};
//Lodash
_.assign(objA,objB);
_.assign是浅拷贝,和ES6新增的Object.assign函数功能一致(建议优先使用Object.assign).
多层嵌套如果解决,如jquery的$.extend(true,{});
7.筛选属性
//Naive method:Remove an array of keys from object
Object.prototype.remove = function(arr) {
var that = this;
arr.forEach(function(key){
delete(that[key]);
});
};
var objA = {"name":"colin","car":"suzuki","age":17};
//将remove函数挂载在Object原型链上,在此处就可以使用了
objA.remove(['car','age']);
objA; // {"name":"colin"}
//Lodash
objA = _.omit(objA,['car','age']);
//=> {"name": "colin"}
//Lodash
objA = _.omit(objA,['car','age']);
//=> {"name":"colin"}
objA = _.omit(objA,'car');
// => {"name": "colin", "age": 17};
objA = _.omit(objA,_.isNumber);
//_.isNumber是什么函数
// => {"name": "colin"};
大多数情况下,lodash所提供的辅助函数都会比原生的函数更贴近开发需求。在上面的代码中,开发者可以使用数组、字符串以及函数的方式筛选对象的属性,并且最终会返回一个新的对象,中间执行筛选时不会对旧对象产生影响。
//Naive method:Returning a new object with selected properties
Object.prototype.pick = function(arr) {
var _this = this;
var obj = { };
arr.forEach(function(key){
obj[key] = _this[key];
});
return obj;
}
var objA = {"name": "colin", "car": "suzuki", "age": 17};
var objB = objA.pick{['car','age']};
//{"car":"suzuki","age":17}
//Lodash
var objB = _.pick(objA,['car','age']);
//{"car":"suzuki","age":17}
.pick是.omit的相反操作,用于从其他对象中挑选属性生成新的对象。
8.随机元素
var luckDraw = ["Colin","John","James","Lily","Mary"];
function pickRandomPerson(luckyDraw){
var index = Math.floor(Math.random() * (luckyDraw.length - 1));
return luckyDraw[index];
}
pickRandomPerson(luckyDraw);//John
//Lodash
_.sample(luckyDraw,2);//Colin
// Lodash - Getting 2 random item
_.sample(luckyDraw, 2); // ['John','Lily'] //随机挑选两个元素并返回新的数组
_.sample 支持随机挑选多个元素并返回新的数组。
9.针对JSON.parse的错误处理
//using try-catch to handle the JSON.parse error
function parse (str) {
try {
return JSON.parse(str);
}
catch(e){
return false;
}
}
// With Lodash
function parseLodash(str) {
return _.attempt(JSON.parse.bind(null,str));
}
parse('a');
//=>false
parseLodash('a');
// =>Return an error object
parse('{"name":"colin"}');
// => Return {"name":"colin"}
parseLodash('{"name":"colin"}');
// => Return {"name":"colin"}
如果你在使用JSON.parse时没有预置错误处理,那么它很有可能会成为一个定时炸弹,我们不应该默认接收的JSON对象都是有效的。try-catch是最常见的错误处理方式,如果项目中Lodash,那么可以使用_.attmpt替代try-catch的方式,当解析JSION出错时,改方法会返回一个Error对象。
随着 ES6 的普及,Lodash 的功能或多或少会被原生功能所替代,所以使用时还需要进一步甄别,建议优先使用原生函数,有关 ES6 替代 Lodash 的部分,请参考文章《10 Lodash Features You Can Replace with ES6》(中文版《10 个可用 ES6 替代的 Lodash 特性》)。
其中有两处非常值得一看:
//使用箭头函数创建可复用的路径:
const object = { 'a': [ {'b': {'c':3} },4] };
[
obj => obj.a[0].b.c,
obj => obj.a[1]
].map(path => path(object));
//使用箭头函数编写链式调用
const pipe = functions => data => {
return functions.reduce(
(value,func) => func(value),
data
);
};
const pipeline = pipe([
x => x * 2,
x => x / 3,
x => x > 5,
b => !b
]);
pipeline(5);
//true
pipeline(20);
//false
在ES6中,如果一个函数只接受一个形参且函数体是一个return语句,就可以使用箭头函数简化为:
const func = p => v;
//类似于(不完全相同)
const func = function (p) {
return v;
}
当有多重嵌套时,可以简化为:
const func = a => b => c => a+b+c;
func(1)(2)(3);
// => 6
//类似于
const func = function (a){
return function (b) {
return function (c) {
return a + b + c;
}
}
}
const objects = {'a':[{'b':{'c':3}},4]};
// 这种写法存在问题吗??????map出现的结果为什么是undefined
[function (obj){
obj.a[0].b.c
},function(obj){
obj.a[1]
}].map(function (path) {
console.log(path(objects));
path(objects);
});
//使用箭头函数编写链式调用
const pipe = functions => data => {
return functions.reduce(
(value,func) => func(value),
data
);
};
const pipe = function functions(){
return function data() {
return functions.reduce(
function(value,func){
func(value);
}
)
}
}
const pipeline = pipe([
x => x * 2,
x => x /3,
x => x > 5,
b => !b
]);
作者:pinggod
链接:https://www.jianshu.com/p/7436e40ac5d1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章:https://www.zhihu.com/question/36942520
10 个可用 ES6 替代的 Lodash 特性
参考来源:http://www.zcfy.cc/article/10-lodash-features-you-can-replace-with-es6-467.html
Lodash现在是npm上被依赖最多的包,如果你现在使用ES6的话,实际上你可能不再需要它了。
1.Map,Filter,Reduce
这些集合方法使数据转化变得轻而易举。由于普遍地对此特性的支持,我们可以将它与箭头函数组合起来,以助我们使用比 Lodash 的实现更简便的方法来实现。
_.map([1,2,3],function(n){ return n * 3; });
// [3, 6, 9]
_.reduce([1, 2, 3], function(total, n) { return total + n; }, 0);
// 6
_.filter([1, 2, 3], function(n) { return n <= 2; });
// [1, 2]
//变为
[1,2,3].map(n => n * 3);
[1,2,3].reduce((total,n) => total + n);
[1, 2, 3].filter(n => n <= 2);
不止于此,如果我们使用 ES6 的 polyfill,我们也能使用 find、some、every 和 reduceRight
- Head & Tail
解构语法 让我们可以获取一个列表的头(head)和尾(tail),而无需工具函数。_.head([1, 2, 3]); // 1 _.tail([1, 2, 3]); // [2, 3] //变为 const [head, ...tail] = [1, 2, 3];
也可以用相似的方式获得列表最后一个元素(last)以及除其之外的元素(initial)
```javascript
_.initial([1, 2, 3]);
// -> [1, 2]
_.last([1, 2, 3]);
// 3
// 变为
const [last, ...initial] = [1, 2, 3].reverse();
如果你讨厌 reverse 会改变数据结构,那你可以在调用 reverse 之前使用延展操作符(spread operator)来复制一个数组。
const xs = [1,2,3];
const [last,...initial] = [...xs].reverse( );
3.Rest & Spread
rest 和 spread函数能让我们定义与调用能接受参数数量不定的函数。es6为这两种操作引入了专用的语法。
var say = _.rest(function (what,names) {
var last = _.last(names);
var initial = _.initial(names);
var finalSeparator = (_.size(names) > 1 ? ', &' : ' ');
return what + ' ' + initial.join(', ') + finalSeparator + _.last(names);
});
//what代表实参‘hello’,names代表实参'fred','barney','pebbles'
say('hello','fred','barney','pebbles');
// "hello fred, barney, & pebbles"
//变为
const say = (what,...names)=> {
const [last,...initial] =names.reverse( );
const finalSeparator = (names.length > 1 ? ', &' : ' ');
return `${what} ${initial.join('. ')} ${finalSeparator} ${last}`;
};
say("hello","fred","barney","pebbles");
// "hello fred, barney, & pebbles"
- Curry
如果没有更高级的语言如TypeScript 和 Flow 的支持,我们不能给函数设置类型签名,这使得函数的柯里化(currying)非常困难。当我们接收一个柯里化的函数时,很难知道已经应用了多少参数以及我们接下来该提供什么参数。通过箭头函数,我们能显示地定义柯里化函数,使得它们对其他程序员来说非常易于理解。function add(a,b) { return a + b; } var curriedAdd = _.curry(add); var add2 = curriedAdd(2); add2(1); //3 //变为 const add = a => b => a + b; //需要再思考一下 const add2 = add(2); add2(1); //3
这些显式的柯里化箭头函数对调试非常重要。
var lodashAdd = _.curry(function(a,b) { return a + b; }); var add3 = lodashAdd(3); console.log(add3.length); //0 console.log(add3); // function wrapper() { // var length = arguments.length, // args = Array(length), // index = length;
// while (index--) {
// args[index] = arguments[index];
// }
// }
//变为
const es6Add = a => b => a + b;
const add3 = es6Add(3);
console.log(add3.length);
//1
console.log(add3);
// function b => a + b
如果我们使用函数式的库如 lodash/fp 和 ramda,那我们也能使用箭头函数来免除对自动柯里化风格的需要。
```javascript
_.map(_.prop('name'))(people);
//变为
people.map(person => person.name);
5.Partial
正如柯里化一样,我们也能使用箭头函数来简化显示化偏函数用法。
var greet = function(greeting, name) {
return greeting + ' ' + name;
};
var sayHelloTo = _.partial(greet,'hello');
sayHelloTo('fred');
// "hello fred"
//变化
const sayHelloTo = name => greet('hello',name);
sayHelloTo('fred');
//"hello fred"
我们也能结合剩余参数与延展操作符来部分地应用可变参数函数
const sayHelloTo = (name, ...args) => greet('hello',name, ...args);
sayHelloTo('fred',1,2,3);
// 'hello fred'
- Operators
Lodash把很多句法操作符重新实现成了函数,所以他们可以被传入集合方法。
在大多数情况下,箭头函数能让它们定义地足够简单精炼,一行足矣。_.eq(3,3); //true _.add(10,1); //11 _.map([1,2,3],function(n) { return _.multiply(n,10); }); // [10,20,30] _.reduce([1,2,3], _.add); //6 //变为 3 === 3 10 + 1 [1,2,3].map(n => n * 10); [1,2,3].reduce((total,n) => total + n);
7.Paths
许多Lodash的函数把路径当做字符串或者数组。然而我们可以使用箭头函数来创建更多可重用的路径。var object = {'a':[{'b':{'c':3}},4]}; _.at(object,['a[0].b.c','a[1]']); // [3,4] _.at(['a','b','c'],0,2); // ['a','c'] //变为 [ obj => obj.a[0].b.c, obj => obj.a[1] ].map(path => path(object)); [ arr => arr[0], arr => arr[2] ].map(path => path(['a','b','c']));
因为这些路径“只是函数”,所以我们也能复合他们。
const getFirstPerson = people => people[0]; const getPostCode = person => person.address.postcode; const getFirstPostCode = prople => getPostCode(getFirstPerson(people)); 我们甚至能创建更高阶能接收参数的路径。 const getFirstNPeople = n => people => people.slice(0,n); const getFirst5People = getFirstNPeople(5); const getFirst5PostCodes = people => getFirst5People(people).map(getPostCode);
8.Pick
pick工具能让我们从一个对象中选择我们想要的属性。我们也能通过解构与对象字面量简写来获取同样的结果。var object = {‘a’:1,'b':2,'c':3}; return _.pick(object,['a','c']); //{a:1,c:3} //变为 const {a,c} = {a:1,b:2,c:3}; return {a,c};
9.Constant,Identity,Noop
Lodash提供了一些工具函数来创建简单的具有某一特定行为的函数。_.constant({'a':1})( ); //{a:1} _.identify({user:'fred'}); //{user:'fred'} _noop( ); //undefined
我们也能使用箭头函数行内定义这些函数。
const constant = x => () => x; const identity = x => x; const noop = () => undefined;
或者我们也能把上面的例子重写为:
(() => ({ a: 1 }))(); // { a: 1 } (x => x)({ user: 'fred' }); // { user: 'fred' } (() => undefined)(); // undefined
10.Chaining &Flow
Lodash 提供了一些函数来帮我们编写链式的语句。在大多情况下,内置的集合函数会返回一个数组实例,能直接被链式调用。但某些情况下,这些方法会改变这个集合,这样就不可能再直接地链式调用了(译者注:需要自己返回实例)。
然而我们也能以一个箭头函数数组来定义同样的转化。_([1, 2, 3]) .tap(function(array) { // 修改输入函数 array.pop(); }) .reverse() .value(); // [2, 1]
// 变为
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
这样的话,我们甚至不需要去思考 tap 和 thru 的不同。把这个归约包装成工具函数能创造出一个非常有用的多功能工具。
```javascript
const pipe = functions => data => {
return functions.reduce(
(value, func) => func(value),
data
);
};
const pipeline = pipe([
x => x * 2,
x => x / 3,
x => x > 5,
b => !b
]);
pipeline(5);
// true
pipeline(20);
// false
总结
Lodash 仍然还是一个非常优秀的库,这篇文章只提供了一个新鲜的观点,JavaScript 的进化版是如何让我们在一些我们之前可能不得不依赖一些工具模块的场景里允许我们直接解决问题的。
不要忽略它(译者注:Lodash),但——下次你需求一个抽象时——思考一下是否一个简单的函数就能办到了!

js的try catch
jquery 让 dom 操作不报错,结果现在很多前端以为 try catch 都用不着了。
https://github.com/inexorabletash/polyfill/blob/master/es5.js
这里有个例子,通过try catch 监测浏览器是否实现某个功能是个好习惯,如果不支持,有时候可以采用降级处理。
那些不处理的异常的,估计也就写写网页吧,一个严肃的应用出现js报错是非常影响用户体验的事情!
如果你无法降级处理,做法也不应该是直接抛出异常,好的体验应该是告诉用户你的应用部分功能不支持他现在的浏览器,提示他更换,抛错会让用户无所适从。
下面用符合 ECMAscript 规范的简单的 JavaScript 来编写相同的“条件catch子句”(显然更加冗长的,但是可以在任何地方运行):
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch
try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}
try{
//正常执行
}catch(e/你感觉会出错的 错误类型/){
//可能出现的意外
e.g:用户自己操作失误 或者 函数少条件不影响下面的函数执行
//有时也会用在 比如focus()但ie有可能会第一次没有focus事件 再让他执行一次
//有时一些不是bug的bug 在ie上要求必须加上catch,哪怕就一个空catch
}
try catch的使用,永远应该放在你的控制范围之内,而不应该防范未知的错误。也就是说你很清楚知道这里 是有可能“出错的”,而且你很清楚知道什么前提下会出错,你就是要故意利用报错信息来区分错误,后续的程序会解决所有的出错,让程序继续执行。
不能让用户发现你根本没预料到的错误,而不是你先发现错误。
大多数情况下,try catch适用于两种场合:
1、浏览器原罪的场合:也就是兼容性场合,因为浏览器兼容性不是程序员能改正的,所以只能try catch:由于不同浏览器的报错提示是不一样的,根据捕获的浏览器的报错提示判断用户的浏览器,然后做出对应的措施,这时候使用try catch是巧妙的办法,如果用if就比较笨拙,因为if通常只能反馈真或假,不能直接反馈浏览器的报错内容。
2、考虑如下代码。window.a.b是非法的,再跟2对比就没有意义,这样非法的条件,在try catch中仍可以继续运行下去。但在if中window.a.b已经报错,整个页面都会坏掉。如果希望用if写,那么必须先判断window.a是否是合法的,window.a是合法的前提下再判断window.a.b是不是合法的,如果也是合法的,再判断window.a.b是否不等于2,这样是不是很蠢?这时就体现出try catch的妙处了,程序不但知道window.a.b !== 2是假的,而且直接可以知道究竟哪一步就已经是假的。
再想象一下,有一个变量是json.a.b.c,其中的a/b/c都可能是存在的也可能是不存在的,全看具体情况,这时候你简单的写if (json.a.b.c === 2) {...}是不行的,因为json.a.b就可能已经是非法的,所以你如果用if,就要考虑a是不是非法的、a是合法前提下b是不是非法的,b是合法前提下c是不是非法的。但是json.a.b.c === 2在try中就可以直接写,也就是说,我不关心究竟a/b/c谁是非法的,我只关心json.a.b.c到底是不是等于2,不等于2或者任何一步出错,对我来讲没有区别,反正都是不等于2,我不关心哪步出错,而且程序不会坏掉。这是一种比较省心的写法。
另外注意,try catch不能做真假判断,只能做非法判断。也就是说:try {1 === 2},虽然1===2是假,但是是合法的,catch不会捕捉到错误,也不会告诉你1 === 2到底是真是假。所以,写在try里的应该是json.a.b.c而不是json.a.b.c === 2。究竟是不是等于2,是后面的事,是if干的事。简单说,try catch用于捕捉报错,当你不关心哪一步错误,只关心有没有错,就用try catch。
try{
window.a.b !== 2;
}catch(err){
alert(err) //可执行
alert(123)//可执行
}
if(window.a.b !== 2){
alert("error") //不执行
}
alert(123) //不执行
try catch在早期被各种语言的程序员滥用,try catch出现的场合就被夸大了,事实上没那么多适用场合。如果你的几千行程序都没用到try catch也是很正常的,尤其是用了jquery。
-
1.事情还有得挽回,换条路走
try {
执行某个逻辑
} catch(e){
出问题了,换个逻辑执行
}
2.体面的退出
try{
正常流程
}catch(e){
弹个框告诉用户不好意思处理点问题
如果是用户的错误就告诉用户什么地方错了
如果是程序的错,就告诉用户不好意思没法执行
} -
异常处理和错误处理是两个不同的概念。例如NodeJS里大多数error都是用来处理异常的,因为异常是不可避免的,例如:数据库挂了,网络错误,你虽然知道有可能,但是不知道何时发生,这些异常你需要捕捉或者传给上层。而错误处理,这是一些基本的判定,可以从代码级别避免其发生,可预知可推测,如果发生了,不是系统问题,而是你的程序有bug了。
对于NodeJS来说,两种错误都时刻需要注意,特别是系统错误,因为不可预知,需要大量代码来catch错误,传递错误,最后统一处理。
而对于前端,系统错误出现的场景相对来说低得多,主要是一些i/o场景,大部分前端可能不太关心。而普通的错误处理,则比较常见,因为前端耦合的特定系统比较多,和这些系统操作的时候,数据和dom大多是可预知的,跟系统错误还是要区分开的,一些错误,需要你自己去吞并和处理,如果出现错误,显然是bug,而不是不可预知。
一句话解释:
try catch机制非常好。那些觉得try catch不行的人,是他们自己的水平有问题,无法理解这种机制。并且这群人写代码不遵守规则,喜欢偷懒,这才造成try catch不好的错觉。
详细解释:
1.程序要健壮,必须要设计报错机制。
最古老,也是最常见的,比如:
bool CreateFile( );
//如果创建文件失败就返回false,否则返回true。
这种报错方式,显然不好。因为它没有给出产生错误的具体原因。
2.改进:一个函数或过程,会因为不同的原因产生错误,报错机制必须要把这些错误原因进行区分后,再汇报。
比如:
int CreateFile():
//如果创建成功就返回1.
//如果是因为没有权限,导致失败,返回-1。
//如果是因为文件已经存在,导致失败,返回-2。
//如果是因为创建文件发生超时,导致失败,返回-3。
这样看上去,比【1】要好些,至少指出了比较具体的失败原因,但是,还不够。
3.很多情况下,函数需要把详细的原因,用字符串的方式,返回:
class Result{
....int State;//同【2】
....string ErrorMessage;//如果失败,这里将给出详细的信息,如果有可能,应该把建议也写上去。
}
Result CreateFile();
//如果创建成功,返回的Result,State为1,ErrorMessage为null。
//如果是因为没有权限,导致失败,返回的Result,State为-1,ErrorMessage为"用户【guest】没有权限在【C:\】这个目录下创建该文件。建议您向管理员申请权限,或者更换具有权限的用户。"。//如果是因为文件已经存在,导致失败,返回的Result,State为-2,ErrorMessage为"文件【C:\abc.txt】已经存在。如果需要覆盖,请添加参数:arg_overwrite = true"。//如果是因为创建文件发生超时,导致失败,返回的Result,State为-3,ErrorMessage为"在创建文件时超时,请使用chkdsk检查文件系统是否存在问题。"。
4.我个人推崇上面这种方式,完整,美观。但是这种流程,容易与正常的代码混在一起,不好区分开。因此,Java、C#等设计了try catch这一种特殊的方式:void CreateFile()//如果创建成功就不会抛出异常。//如果是因为没有权限,导致失败,会抛出AccessException,这个Exception的Msg属性为"用户【guest】没有权限在【C:\】这个目录下创建该文件。建议您向管理员申请权限,或者更换具有权限的用户。"。//如果是因为文件已经存在,导致失败,会抛出FileExistedException,这个Exception的Msg属性为"文件【C:\abc.txt】已经存在。如果需要覆盖,请添加参数:arg_overwrite = true"。//如果是因为创建文件发生超时,导致失败,会抛出TimeoutException,这个Exception的Msg属性为"在创建文件时超时,请使用chkdsk检查文件系统是否存在问题。"。可见,上述机制,实际上是用不同的Exception代替了【3】的State。
这种机制,在外层使用时:
try{
....CreateFile( "C:\abc.txt" );
}catch( AccessException e ){
....//代码进入这里说明发生【没有权限错误】
}
catch( FileExistedException e )
{
....//代码进入这里说明发生【文件已经存在错误】
}catch( TimeoutException e ){
....//代码进入这里说明发生【超时错误】
}
对比一下【3】,其实这与【3】本质相同,只是写法不同而已。
5.综上,我个人喜欢【3】这类面向过程的写法。但很多喜欢面向对象的朋友,估计更喜欢【4】的写法。然而【3】与【4】都一样。这两种机制都是优秀的错误处理机制。
- 理论说完了,回到正题,题注问:为什么不用try catch?
这是因为,很多新手,他们是这样写代码的:
void CreateFile( )
//无论遇到什么错误,就抛一个 Exception,并且也不给出Msg信息。
这样的话,在外层只能使用:
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代码进入这里说明发生错误
}
当出错后,只知道它出错了,并不知道是什么原因导致错误。这同【1】。
以及,即使CreateFile是按【4】的规则设计的,但新手在外层是这样使用的:
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代码进入这里说明发生错误
....throw Exception( "发生错误" )
}
这种情况下,如果这位新手的同事,调用了这段代码,或者用户看到这个错误信息,也只能知道发生了错误,但并不清楚错误的原因。这与【1】是相同的。出于这些原因,新手的同事,以及用户,并没有想到,造成这个问题是原因新手的水平太差,写代码图简单省事。他们却以为是try catch机制不行。因此,这就导致了不建议用try catch。
<script language="JavaScript">
try
{
throw new Error(10,"asdasdasd")
}
catch (e)
{
alert(e.message);
alert(e.description)
alert(e.number)
alert(e.name)
throw new Error(10,"asdasdasd")
}
</script>
在JavaScript可以使用try...catch来进行异常处理。例如:
try {
foo.bar();
} catch (e) {
alert(e.name + ": " + e.message);
}
目前我们可能得到的系统异常主要包含以下6种:
EvalError: raised when an error occurs executing code in eval()
RangeError: raised when a numeric variable or parameter is outside of its valid range
ReferenceError: raised when de-referencing an invalid reference
SyntaxError: raised when a syntax error occurs while parsing code in eval()
TypeError: raised when a variable or parameter is not a valid type
URIError: raised when encodeURI() or decodeURI() are passed invalid parameters
上面的六种异常对象都继承自Error对象。他们都支持以下两种构造方法:
new Error();
new Error("异常信息");
手工抛出异常的方法如下:
try {
throw new Error("Whoops!");
} catch (e) {
alert(e.name + ": " + e.message);
}
如要判断异常信息的类型,可在catch中进行判断:
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
alert(e.name + ":" + e.message);
}
else if (e instanceof RangeError) {
alert(e.name + ": " + e.message);
}
// etc
}
Error具有下面一些主要属性:
description: 错误描述 (仅IE可用).
fileName: 出错的文件名 (仅Mozilla可用).
lineNumber: 出错的行数 (仅Mozilla可用).
message: 错误信息 (在IE下同description)
name: 错误类型.
number: 错误代码 (仅IE可用).
stack: 像Java中的Stack Trace一样的错误堆栈信息 (仅Mozilla可用).
因此为了更好的了解错误信息我们可以将catch部分改为如下形式:
try {
foo.bar();
} catch (e) {
if (browserType != BROWSER_IE) {
alert("name: " + e.name +
"message: " + e.message +
"lineNumber: " + e.lineNumber +
"fileName: " + e.fileName +
"stack: " + e.stack);
}
else {
alert("name: " + e.name +
"errorNumber: " + (e.number & 0xFFFF ) +
"message: " + e.message");
}
}
JavaScript中的throw命令事实上可以抛出任何对象,并且我们可以在catch接受到此对象。例如:
try {
throw new Date(); // 抛出当前时间对象
} catch (e) {
alert(e.toLocaleString()); // 使用本地格式显示当前时间
}
之前一直没有去研究try catch对代码运行的性能影响,只是一直停留在了感觉上,正好最近开会交流学习的时候,有人提出了相关的问题。借着周末,正好研究一番。
window.JSTracker=window.JSTracker||[];
try{
//your code
}catch(e){
JSTracker.push(e);
throwe;//建议将错误再次抛出,避免测试无法发现异常
}
设计实验方式
简单的设计方案也就是对比实验。
空白组1:[无 try catch 的情况下对数据取模1千万次耗时]
<!DOCTYPEhtml>
<html>
<head>
<title>1无trycatch的情况耗时</title>
<script>
!function(){
//无try catch的情况耗时
var t = newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组2:[将耗时代码用 try 包围,内联耗时代码]
<!DOCTYPEhtml>
<html>
<head>
<title>2在try中内联代码的耗时情况</title>
<script>
!function(){
//在 try 中内联代码的耗时情况
vart=newDate();
try{
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
thrownewError();
}catch(e){
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组3:[将耗时代码用 try 包围,外联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>3在try中内联代码的耗时情况</title>
<script>
!function(){
functionrun(){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
//在 try 中内联代码的耗时情况
vart=newDate();
try{
run();
thrownewError();
}catch(e){
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组4:[将耗时代码用 catch 包围,内联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>4在catch中内联代码的耗时情况</title>
<script>
!function(){
//在 catch 中内联代码的耗时情况
vart=newDate();
try{
thrownewError();
}catch(e){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组5:[将耗时代码用 catch 包围,外联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>5在catch中内联代码的耗时情况</title>
<script>
!function(){
functionrun(){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
//在 catch 中内联代码的耗时情况
vart=newDate();
try{
thrownewError();
}catch(e){
run();
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
运行结果(只选取了 Chrome 作为示例)
– 不使用 TRY-CATCH TRY 中耗时,内联代码 TRY 中耗时,外联代码 CATCH 中耗时,内联代码 CATCH 中耗时,外联代码
Chrome51 98.2 1026.9 107.7 1028.5 105.9
给出总结
使用 try catch 的使用无论是在 try 中的代码还是在 catch 中的代码性能消耗都是一样的。
需要注意的性能消耗在于 try catch 中不要直接塞进去太多的代码(声明太多的变量),最好是吧所有要执行的代码放在另一个 function 中,通过调用这个 function 来执行。
针对第二点,可以查看 ECMA 中关于 try catch 的解释,在代码进入 try catch 的时候 js引擎会拷贝当前的词法环境,拷贝的其实就是当前 scope 下的所有的变量。
建议
在使用 try catch 的时候尽量把 try catch 放在一个相对干净的 scope 中,同时在 try catch 语句中也尽量保证足够少的变量,最好通过函数调用方式来 try catch。
试验中的现象解释
测试过程中还是发现了一个疑问, 以下两段代码在 Chrome 44 中运行出来的结果差距非常大,加了一句空的 try catch 之后平均为:850ms,加上之前为:140ms。
?
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
try{
}catch(e){
}
}();
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
其实原因很简单
只要把代码改为这样 耗时就降下来了:
?
!function(){
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
try{
}catch(e){
}
}();
总结
以上就是关于使用try catch对代码运行性能影响的全部内容。
来源:http://www.jb51.net/article/101291.htm
jquery 让 dom 操作不报错,结果现在很多前端以为 try catch 都用不着了。
https://github.com/inexorabletash/polyfill/blob/master/es5.js
这里有个例子,通过try catch 监测浏览器是否实现某个功能是个好习惯,如果不支持,有时候可以采用降级处理。
那些不处理的异常的,估计也就写写网页吧,一个严肃的应用出现js报错是非常影响用户体验的事情!
如果你无法降级处理,做法也不应该是直接抛出异常,好的体验应该是告诉用户你的应用部分功能不支持他现在的浏览器,提示他更换,抛错会让用户无所适从。
下面用符合 ECMAscript 规范的简单的 JavaScript 来编写相同的“条件catch子句”(显然更加冗长的,但是可以在任何地方运行):
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch
try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}
try{
//正常执行
}catch(e/你感觉会出错的 错误类型/){
//可能出现的意外
e.g:用户自己操作失误 或者 函数少条件不影响下面的函数执行
//有时也会用在 比如focus()但ie有可能会第一次没有focus事件 再让他执行一次
//有时一些不是bug的bug 在ie上要求必须加上catch,哪怕就一个空catch
}
try catch的使用,永远应该放在你的控制范围之内,而不应该防范未知的错误。也就是说你很清楚知道这里 是有可能“出错的”,而且你很清楚知道什么前提下会出错,你就是要故意利用报错信息来区分错误,后续的程序会解决所有的出错,让程序继续执行。
不能让用户发现你根本没预料到的错误,而不是你先发现错误。
大多数情况下,try catch适用于两种场合:
1、浏览器原罪的场合:也就是兼容性场合,因为浏览器兼容性不是程序员能改正的,所以只能try catch:由于不同浏览器的报错提示是不一样的,根据捕获的浏览器的报错提示判断用户的浏览器,然后做出对应的措施,这时候使用try catch是巧妙的办法,如果用if就比较笨拙,因为if通常只能反馈真或假,不能直接反馈浏览器的报错内容。
2、考虑如下代码。window.a.b是非法的,再跟2对比就没有意义,这样非法的条件,在try catch中仍可以继续运行下去。但在if中window.a.b已经报错,整个页面都会坏掉。如果希望用if写,那么必须先判断window.a是否是合法的,window.a是合法的前提下再判断window.a.b是不是合法的,如果也是合法的,再判断window.a.b是否不等于2,这样是不是很蠢?这时就体现出try catch的妙处了,程序不但知道window.a.b !== 2是假的,而且直接可以知道究竟哪一步就已经是假的。
再想象一下,有一个变量是json.a.b.c,其中的a/b/c都可能是存在的也可能是不存在的,全看具体情况,这时候你简单的写if (json.a.b.c === 2) {...}是不行的,因为json.a.b就可能已经是非法的,所以你如果用if,就要考虑a是不是非法的、a是合法前提下b是不是非法的,b是合法前提下c是不是非法的。但是json.a.b.c === 2在try中就可以直接写,也就是说,我不关心究竟a/b/c谁是非法的,我只关心json.a.b.c到底是不是等于2,不等于2或者任何一步出错,对我来讲没有区别,反正都是不等于2,我不关心哪步出错,而且程序不会坏掉。这是一种比较省心的写法。
另外注意,try catch不能做真假判断,只能做非法判断。也就是说:try {1 === 2},虽然1===2是假,但是是合法的,catch不会捕捉到错误,也不会告诉你1 === 2到底是真是假。所以,写在try里的应该是json.a.b.c而不是json.a.b.c === 2。究竟是不是等于2,是后面的事,是if干的事。简单说,try catch用于捕捉报错,当你不关心哪一步错误,只关心有没有错,就用try catch。
try{
window.a.b !== 2;
}catch(err){
alert(err) //可执行
alert(123)//可执行
}
if(window.a.b !== 2){
alert("error") //不执行
}
alert(123) //不执行
try catch在早期被各种语言的程序员滥用,try catch出现的场合就被夸大了,事实上没那么多适用场合。如果你的几千行程序都没用到try catch也是很正常的,尤其是用了jquery。
-
1.事情还有得挽回,换条路走
try {
执行某个逻辑
} catch(e){
出问题了,换个逻辑执行
}
2.体面的退出
try{
正常流程
}catch(e){
弹个框告诉用户不好意思处理点问题
如果是用户的错误就告诉用户什么地方错了
如果是程序的错,就告诉用户不好意思没法执行
} -
异常处理和错误处理是两个不同的概念。例如NodeJS里大多数error都是用来处理异常的,因为异常是不可避免的,例如:数据库挂了,网络错误,你虽然知道有可能,但是不知道何时发生,这些异常你需要捕捉或者传给上层。而错误处理,这是一些基本的判定,可以从代码级别避免其发生,可预知可推测,如果发生了,不是系统问题,而是你的程序有bug了。
对于NodeJS来说,两种错误都时刻需要注意,特别是系统错误,因为不可预知,需要大量代码来catch错误,传递错误,最后统一处理。
而对于前端,系统错误出现的场景相对来说低得多,主要是一些i/o场景,大部分前端可能不太关心。而普通的错误处理,则比较常见,因为前端耦合的特定系统比较多,和这些系统操作的时候,数据和dom大多是可预知的,跟系统错误还是要区分开的,一些错误,需要你自己去吞并和处理,如果出现错误,显然是bug,而不是不可预知。
一句话解释:
try catch机制非常好。那些觉得try catch不行的人,是他们自己的水平有问题,无法理解这种机制。并且这群人写代码不遵守规则,喜欢偷懒,这才造成try catch不好的错觉。
详细解释:
1.程序要健壮,必须要设计报错机制。
最古老,也是最常见的,比如:
bool CreateFile( );
//如果创建文件失败就返回false,否则返回true。
这种报错方式,显然不好。因为它没有给出产生错误的具体原因。
2.改进:一个函数或过程,会因为不同的原因产生错误,报错机制必须要把这些错误原因进行区分后,再汇报。
比如:
int CreateFile():
//如果创建成功就返回1.
//如果是因为没有权限,导致失败,返回-1。
//如果是因为文件已经存在,导致失败,返回-2。
//如果是因为创建文件发生超时,导致失败,返回-3。
这样看上去,比【1】要好些,至少指出了比较具体的失败原因,但是,还不够。
3.很多情况下,函数需要把详细的原因,用字符串的方式,返回:
class Result{
....int State;//同【2】
....string ErrorMessage;//如果失败,这里将给出详细的信息,如果有可能,应该把建议也写上去。
}
Result CreateFile();
//如果创建成功,返回的Result,State为1,ErrorMessage为null。
//如果是因为没有权限,导致失败,返回的Result,State为-1,ErrorMessage为"用户【guest】没有权限在【C:\】这个目录下创建该文件。建议您向管理员申请权限,或者更换具有权限的用户。"。//如果是因为文件已经存在,导致失败,返回的Result,State为-2,ErrorMessage为"文件【C:\abc.txt】已经存在。如果需要覆盖,请添加参数:arg_overwrite = true"。//如果是因为创建文件发生超时,导致失败,返回的Result,State为-3,ErrorMessage为"在创建文件时超时,请使用chkdsk检查文件系统是否存在问题。"。
4.我个人推崇上面这种方式,完整,美观。但是这种流程,容易与正常的代码混在一起,不好区分开。因此,Java、C#等设计了try catch这一种特殊的方式:void CreateFile()//如果创建成功就不会抛出异常。//如果是因为没有权限,导致失败,会抛出AccessException,这个Exception的Msg属性为"用户【guest】没有权限在【C:\】这个目录下创建该文件。建议您向管理员申请权限,或者更换具有权限的用户。"。//如果是因为文件已经存在,导致失败,会抛出FileExistedException,这个Exception的Msg属性为"文件【C:\abc.txt】已经存在。如果需要覆盖,请添加参数:arg_overwrite = true"。//如果是因为创建文件发生超时,导致失败,会抛出TimeoutException,这个Exception的Msg属性为"在创建文件时超时,请使用chkdsk检查文件系统是否存在问题。"。可见,上述机制,实际上是用不同的Exception代替了【3】的State。
这种机制,在外层使用时:
try{
....CreateFile( "C:\abc.txt" );
}catch( AccessException e ){
....//代码进入这里说明发生【没有权限错误】
}
catch( FileExistedException e )
{
....//代码进入这里说明发生【文件已经存在错误】
}catch( TimeoutException e ){
....//代码进入这里说明发生【超时错误】
}
对比一下【3】,其实这与【3】本质相同,只是写法不同而已。
5.综上,我个人喜欢【3】这类面向过程的写法。但很多喜欢面向对象的朋友,估计更喜欢【4】的写法。然而【3】与【4】都一样。这两种机制都是优秀的错误处理机制。
- 理论说完了,回到正题,题注问:为什么不用try catch?
这是因为,很多新手,他们是这样写代码的:
void CreateFile( )
//无论遇到什么错误,就抛一个 Exception,并且也不给出Msg信息。
这样的话,在外层只能使用:
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代码进入这里说明发生错误
}
当出错后,只知道它出错了,并不知道是什么原因导致错误。这同【1】。
以及,即使CreateFile是按【4】的规则设计的,但新手在外层是这样使用的:
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代码进入这里说明发生错误
....throw Exception( "发生错误" )
}
这种情况下,如果这位新手的同事,调用了这段代码,或者用户看到这个错误信息,也只能知道发生了错误,但并不清楚错误的原因。这与【1】是相同的。出于这些原因,新手的同事,以及用户,并没有想到,造成这个问题是原因新手的水平太差,写代码图简单省事。他们却以为是try catch机制不行。因此,这就导致了不建议用try catch。
<script language="JavaScript">
try
{
throw new Error(10,"asdasdasd")
}
catch (e)
{
alert(e.message);
alert(e.description)
alert(e.number)
alert(e.name)
throw new Error(10,"asdasdasd")
}
</script>
在JavaScript可以使用try...catch来进行异常处理。例如:
try {
foo.bar();
} catch (e) {
alert(e.name + ": " + e.message);
}
目前我们可能得到的系统异常主要包含以下6种:
EvalError: raised when an error occurs executing code in eval()
RangeError: raised when a numeric variable or parameter is outside of its valid range
ReferenceError: raised when de-referencing an invalid reference
SyntaxError: raised when a syntax error occurs while parsing code in eval()
TypeError: raised when a variable or parameter is not a valid type
URIError: raised when encodeURI() or decodeURI() are passed invalid parameters
上面的六种异常对象都继承自Error对象。他们都支持以下两种构造方法:
new Error();
new Error("异常信息");
手工抛出异常的方法如下:
try {
throw new Error("Whoops!");
} catch (e) {
alert(e.name + ": " + e.message);
}
如要判断异常信息的类型,可在catch中进行判断:
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
alert(e.name + ":" + e.message);
}
else if (e instanceof RangeError) {
alert(e.name + ": " + e.message);
}
// etc
}
Error具有下面一些主要属性:
description: 错误描述 (仅IE可用).
fileName: 出错的文件名 (仅Mozilla可用).
lineNumber: 出错的行数 (仅Mozilla可用).
message: 错误信息 (在IE下同description)
name: 错误类型.
number: 错误代码 (仅IE可用).
stack: 像Java中的Stack Trace一样的错误堆栈信息 (仅Mozilla可用).
因此为了更好的了解错误信息我们可以将catch部分改为如下形式:
try {
foo.bar();
} catch (e) {
if (browserType != BROWSER_IE) {
alert("name: " + e.name +
"message: " + e.message +
"lineNumber: " + e.lineNumber +
"fileName: " + e.fileName +
"stack: " + e.stack);
}
else {
alert("name: " + e.name +
"errorNumber: " + (e.number & 0xFFFF ) +
"message: " + e.message");
}
}
JavaScript中的throw命令事实上可以抛出任何对象,并且我们可以在catch接受到此对象。例如:
try {
throw new Date(); // 抛出当前时间对象
} catch (e) {
alert(e.toLocaleString()); // 使用本地格式显示当前时间
}
之前一直没有去研究try catch对代码运行的性能影响,只是一直停留在了感觉上,正好最近开会交流学习的时候,有人提出了相关的问题。借着周末,正好研究一番。
window.JSTracker=window.JSTracker||[];
try{
//your code
}catch(e){
JSTracker.push(e);
throwe;//建议将错误再次抛出,避免测试无法发现异常
}
设计实验方式
简单的设计方案也就是对比实验。
空白组1:[无 try catch 的情况下对数据取模1千万次耗时]
<!DOCTYPEhtml>
<html>
<head>
<title>1无trycatch的情况耗时</title>
<script>
!function(){
//无try catch的情况耗时
var t = newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组2:[将耗时代码用 try 包围,内联耗时代码]
<!DOCTYPEhtml>
<html>
<head>
<title>2在try中内联代码的耗时情况</title>
<script>
!function(){
//在 try 中内联代码的耗时情况
vart=newDate();
try{
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
thrownewError();
}catch(e){
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组3:[将耗时代码用 try 包围,外联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>3在try中内联代码的耗时情况</title>
<script>
!function(){
functionrun(){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
//在 try 中内联代码的耗时情况
vart=newDate();
try{
run();
thrownewError();
}catch(e){
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组4:[将耗时代码用 catch 包围,内联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>4在catch中内联代码的耗时情况</title>
<script>
!function(){
//在 catch 中内联代码的耗时情况
vart=newDate();
try{
thrownewError();
}catch(e){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
参照组5:[将耗时代码用 catch 包围,外联耗时代码]
?
<!DOCTYPEhtml>
<html>
<head>
<title>5在catch中内联代码的耗时情况</title>
<script>
!function(){
functionrun(){
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
}
//在 catch 中内联代码的耗时情况
vart=newDate();
try{
thrownewError();
}catch(e){
run();
}
document.write(newDate()–t);
}();
</script>
</head>
<body>
</body>
</html>
运行结果(只选取了 Chrome 作为示例)
– 不使用 TRY-CATCH TRY 中耗时,内联代码 TRY 中耗时,外联代码 CATCH 中耗时,内联代码 CATCH 中耗时,外联代码
Chrome51 98.2 1026.9 107.7 1028.5 105.9
给出总结
使用 try catch 的使用无论是在 try 中的代码还是在 catch 中的代码性能消耗都是一样的。
需要注意的性能消耗在于 try catch 中不要直接塞进去太多的代码(声明太多的变量),最好是吧所有要执行的代码放在另一个 function 中,通过调用这个 function 来执行。
针对第二点,可以查看 ECMA 中关于 try catch 的解释,在代码进入 try catch 的时候 js引擎会拷贝当前的词法环境,拷贝的其实就是当前 scope 下的所有的变量。
建议
在使用 try catch 的时候尽量把 try catch 放在一个相对干净的 scope 中,同时在 try catch 语句中也尽量保证足够少的变量,最好通过函数调用方式来 try catch。
试验中的现象解释
测试过程中还是发现了一个疑问, 以下两段代码在 Chrome 44 中运行出来的结果差距非常大,加了一句空的 try catch 之后平均为:850ms,加上之前为:140ms。
?
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
try{
}catch(e){
}
}();
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
其实原因很简单
只要把代码改为这样 耗时就降下来了:
?
!function(){
!function(){
//无 try catch 的情况耗时
vart=newDate();
//耗时代码开始
for(vari=0;i<100000000;i++){
varp=i%2;
}
//耗时代码结束
document.write(newDate()–t);
}();
try{
}catch(e){
}
}();
总结
以上就是关于使用try catch对代码运行性能影响的全部内容。
来源:http://www.jb51.net/article/101291.htm

js中的对象object
对象是JavaScript的基本数据类型。
对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。
对象也可以看做是属性的无序集合,每个属性都是一个名/值对。
属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。
这种基本数据类型还有很多叫法,有些我们已然非常熟悉,比如“散列”(hash)、“散列表”(hashtable)、“字典”(dictionary)、“关联数组”(associativearray)。然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种“原型式继承”(prototypal inheritance)是JavaScript的核心特征。
JavaScript是动态的———可以新增属性也可以删除属性————但他们常用来模拟静态对象以及静态类型语言中的“结构体”(struct)。有时他们也用做字符串的集合(忽略名/值对中的值)。
除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。尽管字符串、数字和布尔值不是对象,但他们的行为和不可变对象非常类似。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、监测(test)、枚举(enumerate)它的属性。
用术语对三类JavaScript对象和两类属性作区分:
内置对象:是由ECMAScript规范定义的对象或类。例如:数组、函数、日期和正则表达式都是内置对象。
宿主对象:是由JavaScript解释器所嵌入的宿主环境(比如web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLlement对象均是宿主对象。既然宿主环境定义的方法可以当成普通的JavaScript函数对象,那么宿主对象也可以当成内置对象。
自定义对象:是由运行中的JavaScript代码创建的对象。
自有属性:是直接在对象中定义的属性。
继承属性:是在对象在原型对象中定义的属性。
创建对象的方式:
(1)对象直接量:
var book = {
“main title” : “Javascript”,
“sub-title” :“The Definitive Guide”,
author: {
firstname:“David”,
surname:“Flanagan”
}
};
(2)通过new创建对象
var a = new Array();
var d = new Date();
var r = new RegExp(“js”);
(3)原型
每个JavaScript对象(null除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对象原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。因此,同使用{ }创建对象一样,通过new Object()创建的对象也继承自Object.prototype,同样,通过new Array()创建的对象的 原型 就是Array.prototype,通过new Date()创建的对象的 原型 就是Date.prototype.
(4) Object.create( )
var o1 = Object.create({x:1,y:2}); //o1继承了属性的x和y
var o3 = Object.create(Object.prototype); // o3和{ }和new Object()一样
//inherit()返回了一个继承自原型对象p的属性的新对象
//这里使用ECMAScript 5中的Object.create()函数
//如果不存在Object.create(),则退化使用其他方法
function inherit(p) {
if(p === null) throw TypeError(); //p是一个对象,但不能是null
if(Object.create) { //如果Object.create()存在
return Object.create(p); //直接使用它
}
var t = typeof p; //否则进行进一步检测
if(t !== "object" && t !== "function"){
throw TypeError();
}
function f() {}; //定义一个空构造函数
f.prototype = p; //将其原属性设置为p
return new f( ); //使用f()创建p的继承对象
}
属性的查询和设置
可以通过(.)或([ ])运算符来获取属性的值。
作为关联数组的对象
var addr = " ";
for (i = 0; i < 4; i ++){
addr += customer["address" + i] + '\n';
}
这段代码读取customer对象的address0、address1、address2、和address3属性,并将他们连接起来。
很多场景只能使用数组写法来完成。假设你正在写一个程序,这个程序利用网络资源就按当期那用户股票市场投资的金额。程序允许用户输入妹纸股票的名称和购股份额。该程序使用名为portfolio的对象来存储这些信息。每只股票这这个对象中都有对相应的属性,属性名就是股票名称,属性值就是购股数量。例如:如果用户持有IBM的50股,那么portifolio属性的值就为50。
部分代码如下:
function addstock(portfolio,stockname,shares){
protfolio[stockname] = shares;
}
当使用for/in循环遍历关联数组时,就可以清晰地体会到for/in的强大之处。下面的例子就是利用for/in计算portfolio的总计值:
function getvalue(portfolio){
var total = 0.0;
for (stock in portfolio){ //遍历portfilio的每只股票
var shares = portfolio[stock]; //得到每只股票的份额
var price = getquote(stock);//查找股票价格
total += shares * price; //将结果累加至total中
}
return total; //返回total值
}
删除属性
delete云算法可以删除对象的属性。
delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
delete云算符只能删除自有属性,不能删除继承属性。
当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true。
o = {x:1}; //o有一个属性x,并继承属性toString
delete o.x; //删除x,返回true
delete o.x; //什么都没有做,x已经不存在了,返回true
delete o.toString //什么也没做,toString是继承来的,对应第三条,返回true
delete 1; //无意义,返回true
delete不能删除那些可配置性为false的属性。
在以下情况下的delete操作会返回false。
delete Object.prototype; //不能删除,属性是不可配置的。
var x = 1; //声明一个全局变量
delete this.x; //不能删除这个属性
function f(){ }; //声明一个全局函数
delete this.f; //也不能删除全局函数
检测属性
JavaScript对象可以看做属性的集合,我们经常会检测集合中成员的所属关系————判断某个属性是否存在于某个对象中。可以通过in运算符、hasOwnPrepperty()和propertyIsEnumerable()方法来完成这个工作,甚至可以仅通过属性查询也可以做到这一点。
in运算符
var o = {x:1};
"x" in o; //true
"y" in o; //false
"toString" in o; //true:o继承toString属性
hasOwnPreperty()用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false;
o.hasOwnPreperty("x"); //true
o.hasOwnPreperty("y"); //false
o.hasOwnPreperty("toString"); //false,是继承属性
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为true时它才会返回true。某些内置属性是不可枚举的。
除了使用in运算符之外,另一种更简便的方式是使用 “!==”判断一个属性是否是undefined:
var o = {x:1};
o.x !== undefined; //true
o.y !== undefined; //false
o.toString !== undefined; //true:o继承了toString属性
!包含了null和undefined,用来判断属性是否存在和是否为空,在书中某一章节有使用。
枚举属性
除了检测对象的属性是否存在,我们还经常会遍历对象的属性。通常使用for/in循环遍历,es5中提供了两个更好的替代方案。
for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名赋值给循环变量。对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的。
var o = {x:1,y:2,z:3}; //三个可枚举的属性
o.propertyIsEnumerable("toString") //false,不可枚举
for(p in o ) //遍历属性
console.log(p) //输出x,y和z,不会输出toString
例子:用来枚举属性的对象的工具函数
/
把p中的可枚举属性复制到o中,并返回o
如果o和p中含有同名属性,则覆盖o中的属性
这个函数并不处理getter和setter以及复制属性
/
function extend(o,p) {
for (prop in p) { //遍历p中所有属性
o[prop] = p[prop]; //将属性添加至o中
}
return o;
}
/
返回一个新对象,这个对象拥有同时在o和p中出现的属性
如果o和p中有重名属性,使用p中的属性值
*/
function union(o,p){return extend(extend({ }, o),p);}
继承
继承的5种方式要单独一篇文章
对象方法
1.toString()方法
m instanceof Array
toString()方法没有参数,它将返回一个调用这个方法的对象值的字符串。在需要将对象转换为字符串的时候,JavaScript都会调用这个方法。比如,当使用“+”运算符连接一个字符串和一个对象时或者在希望使用字符串的方法使用了对象时都会调用toString()。
var s = {x:1,y:1}.toString(); //"[object Object]"
由于默认的toString()方法并不会输出很多有用的信息,因此很多类都带有自定义的toString()方法。
- toLocalString()方法
- toJSON()方法
- valueOf()方法
valueOf()方法和toString()方法非常类似,但往往当JavaScript需要将对象转换为某种原始值而字符串的时候才会调用它,尤其是转换为数字的时候。尤其是转换为数字的时候。如果在需要使用原始值得上下文中使用了对象,JavaScript就会自动调用这个方法。默认的valueOf()方法不足为奇,但有些内置类自定义了valueOf()方法(比如Date.valueOf())。
参考:https://segmentfault.com/a/1190000010661297?_ea=3321402
对象是JavaScript的基本数据类型。
对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。
对象也可以看做是属性的无序集合,每个属性都是一个名/值对。
属性名是字符串,因此我们可以把对象看成是从字符串到值的映射。
这种基本数据类型还有很多叫法,有些我们已然非常熟悉,比如“散列”(hash)、“散列表”(hashtable)、“字典”(dictionary)、“关联数组”(associativearray)。然而对象不仅仅是字符串到值的映射,除了可以保持自有的属性,JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种“原型式继承”(prototypal inheritance)是JavaScript的核心特征。
JavaScript是动态的———可以新增属性也可以删除属性————但他们常用来模拟静态对象以及静态类型语言中的“结构体”(struct)。有时他们也用做字符串的集合(忽略名/值对中的值)。
除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。尽管字符串、数字和布尔值不是对象,但他们的行为和不可变对象非常类似。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、监测(test)、枚举(enumerate)它的属性。
用术语对三类JavaScript对象和两类属性作区分:
内置对象:是由ECMAScript规范定义的对象或类。例如:数组、函数、日期和正则表达式都是内置对象。
宿主对象:是由JavaScript解释器所嵌入的宿主环境(比如web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLlement对象均是宿主对象。既然宿主环境定义的方法可以当成普通的JavaScript函数对象,那么宿主对象也可以当成内置对象。
自定义对象:是由运行中的JavaScript代码创建的对象。
自有属性:是直接在对象中定义的属性。
继承属性:是在对象在原型对象中定义的属性。
创建对象的方式:
(1)对象直接量:
var book = {
“main title” : “Javascript”,
“sub-title” :“The Definitive Guide”,
author: {
firstname:“David”,
surname:“Flanagan”
}
};
(2)通过new创建对象
var a = new Array();
var d = new Date();
var r = new RegExp(“js”);
(3)原型
每个JavaScript对象(null除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对象原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。因此,同使用{ }创建对象一样,通过new Object()创建的对象也继承自Object.prototype,同样,通过new Array()创建的对象的 原型 就是Array.prototype,通过new Date()创建的对象的 原型 就是Date.prototype.
(4) Object.create( )
var o1 = Object.create({x:1,y:2}); //o1继承了属性的x和y
var o3 = Object.create(Object.prototype); // o3和{ }和new Object()一样
//inherit()返回了一个继承自原型对象p的属性的新对象
//这里使用ECMAScript 5中的Object.create()函数
//如果不存在Object.create(),则退化使用其他方法
function inherit(p) {
if(p === null) throw TypeError(); //p是一个对象,但不能是null
if(Object.create) { //如果Object.create()存在
return Object.create(p); //直接使用它
}
var t = typeof p; //否则进行进一步检测
if(t !== "object" && t !== "function"){
throw TypeError();
}
function f() {}; //定义一个空构造函数
f.prototype = p; //将其原属性设置为p
return new f( ); //使用f()创建p的继承对象
}
属性的查询和设置
可以通过(.)或([ ])运算符来获取属性的值。
作为关联数组的对象
var addr = " ";
for (i = 0; i < 4; i ++){
addr += customer["address" + i] + '\n';
}
这段代码读取customer对象的address0、address1、address2、和address3属性,并将他们连接起来。
很多场景只能使用数组写法来完成。假设你正在写一个程序,这个程序利用网络资源就按当期那用户股票市场投资的金额。程序允许用户输入妹纸股票的名称和购股份额。该程序使用名为portfolio的对象来存储这些信息。每只股票这这个对象中都有对相应的属性,属性名就是股票名称,属性值就是购股数量。例如:如果用户持有IBM的50股,那么portifolio属性的值就为50。
部分代码如下:
function addstock(portfolio,stockname,shares){
protfolio[stockname] = shares;
}
当使用for/in循环遍历关联数组时,就可以清晰地体会到for/in的强大之处。下面的例子就是利用for/in计算portfolio的总计值:
function getvalue(portfolio){
var total = 0.0;
for (stock in portfolio){ //遍历portfilio的每只股票
var shares = portfolio[stock]; //得到每只股票的份额
var price = getquote(stock);//查找股票价格
total += shares * price; //将结果累加至total中
}
return total; //返回total值
}
删除属性
delete云算法可以删除对象的属性。
delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
delete云算符只能删除自有属性,不能删除继承属性。
当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true。
o = {x:1}; //o有一个属性x,并继承属性toString
delete o.x; //删除x,返回true
delete o.x; //什么都没有做,x已经不存在了,返回true
delete o.toString //什么也没做,toString是继承来的,对应第三条,返回true
delete 1; //无意义,返回true
delete不能删除那些可配置性为false的属性。
在以下情况下的delete操作会返回false。
delete Object.prototype; //不能删除,属性是不可配置的。
var x = 1; //声明一个全局变量
delete this.x; //不能删除这个属性
function f(){ }; //声明一个全局函数
delete this.f; //也不能删除全局函数
检测属性
JavaScript对象可以看做属性的集合,我们经常会检测集合中成员的所属关系————判断某个属性是否存在于某个对象中。可以通过in运算符、hasOwnPrepperty()和propertyIsEnumerable()方法来完成这个工作,甚至可以仅通过属性查询也可以做到这一点。
in运算符
var o = {x:1};
"x" in o; //true
"y" in o; //false
"toString" in o; //true:o继承toString属性
hasOwnPreperty()用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false;
o.hasOwnPreperty("x"); //true
o.hasOwnPreperty("y"); //false
o.hasOwnPreperty("toString"); //false,是继承属性
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为true时它才会返回true。某些内置属性是不可枚举的。
除了使用in运算符之外,另一种更简便的方式是使用 “!==”判断一个属性是否是undefined:
var o = {x:1};
o.x !== undefined; //true
o.y !== undefined; //false
o.toString !== undefined; //true:o继承了toString属性
!包含了null和undefined,用来判断属性是否存在和是否为空,在书中某一章节有使用。
枚举属性
除了检测对象的属性是否存在,我们还经常会遍历对象的属性。通常使用for/in循环遍历,es5中提供了两个更好的替代方案。
for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名赋值给循环变量。对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的。
var o = {x:1,y:2,z:3}; //三个可枚举的属性
o.propertyIsEnumerable("toString") //false,不可枚举
for(p in o ) //遍历属性
console.log(p) //输出x,y和z,不会输出toString
例子:用来枚举属性的对象的工具函数
/
把p中的可枚举属性复制到o中,并返回o
如果o和p中含有同名属性,则覆盖o中的属性
这个函数并不处理getter和setter以及复制属性
/
function extend(o,p) {
for (prop in p) { //遍历p中所有属性
o[prop] = p[prop]; //将属性添加至o中
}
return o;
}
/
返回一个新对象,这个对象拥有同时在o和p中出现的属性
如果o和p中有重名属性,使用p中的属性值
*/
function union(o,p){return extend(extend({ }, o),p);}
继承
继承的5种方式要单独一篇文章
对象方法
1.toString()方法
m instanceof Array
toString()方法没有参数,它将返回一个调用这个方法的对象值的字符串。在需要将对象转换为字符串的时候,JavaScript都会调用这个方法。比如,当使用“+”运算符连接一个字符串和一个对象时或者在希望使用字符串的方法使用了对象时都会调用toString()。
var s = {x:1,y:1}.toString(); //"[object Object]"
由于默认的toString()方法并不会输出很多有用的信息,因此很多类都带有自定义的toString()方法。
- toLocalString()方法
- toJSON()方法
- valueOf()方法
valueOf()方法和toString()方法非常类似,但往往当JavaScript需要将对象转换为某种原始值而字符串的时候才会调用它,尤其是转换为数字的时候。尤其是转换为数字的时候。如果在需要使用原始值得上下文中使用了对象,JavaScript就会自动调用这个方法。默认的valueOf()方法不足为奇,但有些内置类自定义了valueOf()方法(比如Date.valueOf())。
参考:https://segmentfault.com/a/1190000010661297?_ea=3321402

js中Array的常用方法
map()方法
map()方法返回一个新数组,数组中的元素为原始数组元素调用函数后处理的值
注意:1.map()不会对空数组进行检测,2.map()不会改变原始数组。
var numbers = [4, 9, 16, 25];
numbers.map(Math.sqrt); //[2,3,4,5]
concat() 连接两个或更多的数组,并返回结果
copyWithin() 从数组的指定位置拷贝元素到数组的另一个指定位置中
every() 检测数值元素的每个元素是否都符合条件
fill() 使用一个固定值来填充数组
filter() 检测数值元素,并返回符合条件所有元素的数组
find() 返回符合传入测试(函数)条件的数组元素
findindex() 返回符合传入测试(函数)条件的数组元素索引
forEach() 数组每个元素都执行一次回调函数
includes() 判断一个数组是否包含一个指定的值
indexOf() 搜索数组中的元素,并返回它所在的位置
join() 把数组的所有元素放入一个字符串
lastIndexOf() 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索
map( ) 通过指定函数处理数组的每个元素,并返回处理后的数组。map()方法定义在js的Array中,
传入自己的函数,得到一个新的Array作为结果:
function pow(x) {
return x * x;
}
var arr = [1,2,3,4,5,6,7,8,9];
app.map(pow);//[1, 4, 9, 16, 25, 36, 49, 64, 81]
map()传入的参数是pow,即函数对象本身
map()作为高阶函数,可以计算任意复杂的函数,比如:把Array的所有数字转为字符串:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
只需要一行代码
pop()删除数组的最后一个元素并返回删除的元素
push()向数组的末尾添加一个或更多元素,并返回新的长度。
reduce()将数组元素计算为一个值(从左到右),这个函数必须接收两个参数。猜测此方法的内部实现为:当前返回的值作为参数参与到下一次函数的计算中,从而从左往右执行。
对array求和:
var arr = [1,3,5,7,9];
arr.reduce(function (x,y){
return x + y;
}); //25
求积
arr.reduce(function (x,y) {
return parseInt(x) * parseInt(y);
});
reduceRight()将数组元素计算为一个值(从右到左)
reverse() 反转数组的元素顺序
shift() 删除并返回数组的第一个元素
slice() 选取数组的一部分,并返回一个新数组
some()检测数组元素中是否有元素符合指定条件
sort() 对数组的元素进行排序
splice()从数组中添加或删除元素
toString()把数组转换为字符串,并返回结果
unshift() 想数组的开头添加一个或多个元素,并返回新的长度
valueOf ( ) 返回数组对象的原始值
ES6新增方法
Array.from()用于将两类对象转为真正的数组:类似数组的对象(array-like-object)和可变遍历(itreable)的对象(包括ES6新增的数据结构Set和Map)。
例如,如下类数组:
let arrayLIke = {
'0' : 'a',
'1' : 'b',
'2' : 'c',
length: 3
}
//ES5的写法
var arr1 = [ ].slice.call(arrayLike); // ['a','b','c']
//ES6的写法
let arr2 = Array.from(arrayLike); //['a','b','c']
数组对象自带length属性(下图为Array的length):
参考链接:https://www.cnblogs.com/jiangyi666/p/5991324.html
map()方法
map()方法返回一个新数组,数组中的元素为原始数组元素调用函数后处理的值
注意:1.map()不会对空数组进行检测,2.map()不会改变原始数组。
var numbers = [4, 9, 16, 25];
numbers.map(Math.sqrt); //[2,3,4,5]
concat() 连接两个或更多的数组,并返回结果
copyWithin() 从数组的指定位置拷贝元素到数组的另一个指定位置中
every() 检测数值元素的每个元素是否都符合条件
fill() 使用一个固定值来填充数组
filter() 检测数值元素,并返回符合条件所有元素的数组
find() 返回符合传入测试(函数)条件的数组元素
findindex() 返回符合传入测试(函数)条件的数组元素索引
forEach() 数组每个元素都执行一次回调函数
includes() 判断一个数组是否包含一个指定的值
indexOf() 搜索数组中的元素,并返回它所在的位置
join() 把数组的所有元素放入一个字符串
lastIndexOf() 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索
map( ) 通过指定函数处理数组的每个元素,并返回处理后的数组。map()方法定义在js的Array中,
传入自己的函数,得到一个新的Array作为结果:
function pow(x) {
return x * x;
}
var arr = [1,2,3,4,5,6,7,8,9];
app.map(pow);//[1, 4, 9, 16, 25, 36, 49, 64, 81]
map()传入的参数是pow,即函数对象本身
map()作为高阶函数,可以计算任意复杂的函数,比如:把Array的所有数字转为字符串:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
只需要一行代码
pop()删除数组的最后一个元素并返回删除的元素
push()向数组的末尾添加一个或更多元素,并返回新的长度。
reduce()将数组元素计算为一个值(从左到右),这个函数必须接收两个参数。猜测此方法的内部实现为:当前返回的值作为参数参与到下一次函数的计算中,从而从左往右执行。
对array求和:
var arr = [1,3,5,7,9];
arr.reduce(function (x,y){
return x + y;
}); //25
求积
arr.reduce(function (x,y) {
return parseInt(x) * parseInt(y);
});
reduceRight()将数组元素计算为一个值(从右到左)
reverse() 反转数组的元素顺序
shift() 删除并返回数组的第一个元素
slice() 选取数组的一部分,并返回一个新数组
some()检测数组元素中是否有元素符合指定条件
sort() 对数组的元素进行排序
splice()从数组中添加或删除元素
toString()把数组转换为字符串,并返回结果
unshift() 想数组的开头添加一个或多个元素,并返回新的长度
valueOf ( ) 返回数组对象的原始值
ES6新增方法
Array.from()用于将两类对象转为真正的数组:类似数组的对象(array-like-object)和可变遍历(itreable)的对象(包括ES6新增的数据结构Set和Map)。
例如,如下类数组:
let arrayLIke = {
'0' : 'a',
'1' : 'b',
'2' : 'c',
length: 3
}
//ES5的写法
var arr1 = [ ].slice.call(arrayLike); // ['a','b','c']
//ES6的写法
let arr2 = Array.from(arrayLike); //['a','b','c']
数组对象自带length属性(下图为Array的length):
参考链接:https://www.cnblogs.com/jiangyi666/p/5991324.html
收起阅读 »
js中Object常用方法和属性
Object的常用方法和属性也是很常用的。
一、属性
Object自带一个prototype的属性,即Object.prototype, Object.prototype本身也只是一个对象,也会有一些属性和方法。
1.属性
Object.prototype.writable:默认为false
Object.prototype.enumerable:默认为false
Object.prototype.configurable:默认为false
Object.prototype.constructor:用于创建一个对象的原型。
2、常用方法
Object.prototype.hasOwnProperty():返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承。
Object.prototype.isPrototypeOf():返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable():判断指定属性是否可枚举。
Object.prototype.toString():返回对象的字符串表示。
Object.prototype.watch():给对象的某个属性增加监听。
Object.prototype.unwatch():移除对象某个属性的监听。
Object.prototype.valueOf():返回指定对象的原始值。
二、方法
Object.assign(target,...sources):把任意多的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
Object.create(proto,[propertiesobject]):创建一个拥有指定原型和若干个指定属性的对象。
Object.defineProperty(obj,prop,descriptor):直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。obj:需要定义属性的对象。prop:需定义或修改的属性的名字。descriptor:将被定义或修改的属性的描述符。
Object.entries(obj):返回一个包含由给定对象所有可枚举属性的属性名和属性值组成的 [属性名,属性值] 键值对的数组,数组中键值对的排列顺序和使用for…in循环遍历该对象时返回的顺序一致。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.entries(obj)); // [ [‘foo’, ‘bar’], [‘baz’, 42] ]
Object.freeze(obj):冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
Object.getOwnPropertyNames(obj):返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组。
举例:
// 类数组对象
var obj = { 0: “a”, 1: “b”, 2: “c”};
console.log(Object.getOwnPropertyNames(obj).sort()); // [“0”, “1”, “2”]
Object.getPrototypeOf(object):返回该对象的原型。
Object.is(value1, value2):判断两个值是否是同一个值。
Object.isExtensible(obj):判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.isFrozen(obj):判断一个对象是否被冻结(frozen)。
Object.isSealed(obj):判断一个对象是否是密封的(sealed)。密封对象是指那些不可 扩展 的,且所有自身属性都不可配置的(non-configurable)且属性不可删除的对象(其可以是可写的)。
Object.keys(obj):返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致
举例:
var arr = [“a”, “b”, “c”];
alert(Object.keys(arr)); // 弹出”0,1,2”
// 类数组对象
var obj = { 0 : “a”, 1 : “b”, 2 : “c”};
alert(Object.keys(obj)); // 弹出”0,1,2”
Object.preventExtensions(obj):让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.setPrototypeOf(obj, prototype):将一个指定的对象的原型设置为另一个对象或者null
Object.values(obj):返回一个包含指定对象所有的可枚举属性值的数组,数组中的值顺序和使用for…in循环遍历的顺序一样。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.values(obj)); // [‘bar’, 42]
Object.assign()与深拷贝
深拷贝与浅拷贝
所谓深拷贝与浅拷贝,是围绕引用类型变量的拷贝进行的讨论。
在ecmascript中,变量分为基本类型和引用类型两种。其本质区别是不可变性,基本类型是不可变的,而引用类型是可变的。
所谓基本类型的不可变性,我们可以举个例子
let a = 1;
let b = 1;
a++;
a===2;//true
b===1;//true
声明一个变量a,并为其赋值1,这时内存中开辟出一片区域用来储存1。此时声明了一个变量b,也为b赋值1。当执行a++时,
基本类型的不可变性就体现出来,a++的值应该为2,但是这个值并不会将原来储存1的那片内存覆盖掉,而是再开辟一片内
存来存储2。所以对于这个1来讲,他是永远不可变的。
而对于引用变量则不同,因为其存储的是只想某个内存区域的地址,所以其修改时直接操作在内存上的,
这就导致了深拷贝和浅拷贝问题的出现。
浅拷贝
let foo = {
x: 1,
y: -1
}
let bar = foo;
foo.x++;
foo.x ===2 //true
bar.x ===2 //true
这就是最简单的浅拷贝,其效果十分明显,对拷贝源的操作,会直接体现在拷贝目标上,因为这个赋值行为的本质是内存地址的赋值,
所以他们指向了同一片内存区域。
浅拷贝十分容易,也十分常见,但却无法满足需求,加入我们需要获得拷贝源完全相同,却又不会互相影响的对象,应该怎么办呢
Object.assign()
ES6为我们提供了一种十分好用的方法,Object.assign(target, ...source)方法
assign方法接受多个参数,第一个参数target为拷贝目标,剩余参数...source是拷贝源。此方法可以将...source中的属性复制到target中,
同名属性会进行覆盖,并且在复制过程中实现了'伪'深拷贝
let foo = {
a: 1,
b: 2,
c: {
d: 1
}
}
let bar = { };
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
乍一看,好像已经实现了深拷贝的效果,对foo.a进行的操作并没有体现在bar.a中,但是再往后看
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()的拷贝类型十分明显了,这是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝.
如果真的想进行深拷贝,最简单粗暴地方式就是JSON操作。
JSON对象中包含两个方法,stringify( )和parse( ),前者可以将对象JSON化,而后者可以将JSON格式转换为对象,这是一种可以实现深拷贝的方法。
但这种方法的缺陷是会破坏原型链,并且无法拷贝属性值为function的属性
所以如果只是想单纯复制一个嵌套对象,可以使用此方法
let foo = {
a:1,
b: {
c: 1
}
}
let bar = JSON.parse(JSON.stringify(foo));
来源:https://segmentfault.com/a/1190000010661297
es6入门
Object.assign方法用于对象的合并
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一个参数,Object.assign会直接返回该参数。
const obj = {a:1};
Object.assign(obj) === obj //true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举属性。
Object.assign({b: 'c'},
Object.defineProperty({},'invisible',{
enumerable: false,
value: 'hello'
})
}
//{b: 'c'}
注意点
(1)浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({},obj1);
obj1.a.b = 2;
obj2.a.b //2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
source1.b={m:6}
Object.assign(target,source1,source2)
target ={a: 1, b:{m:6}, c: 3}
target.b.m = 8; //修改m的值
source1.b.m //8 说明是浅拷贝
原因分析:string、boolean、number是按值传递的,array、obj是按引用传递。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。
这通常不是开发者想要的,需要特别小心。
一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。
jquery的$.extend(true,{})可以进行深层合并
常见用途
Object.assign方法有很多用处
(1)为对象添加属性
class Point {
constructor (x,y) {
Object.assign(this,{x,y});
}
}
const source1 = {b:2};
const source2 = {c:3};
(2)为对象添加方法
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2) {
},
anotherMethod( ) {
}
})
//等同于
SomeClass.prototype.someMethod = function (arg1,arg2) {
};
SomeClass.prototype.anotherMethod = function ( ) {
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,
再使用assign方法添加到SomeClass.prototype之中。
(3)克隆对象
function clone(origin) {
return Object.assign({},origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,用这种方法克隆,只能克隆原始对象自身的值(也就是浅拷贝),不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}
(4)合并多个对象
将多个对象合并到某个对象。
const merge = (target,...sources) => Object.assign(target,...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge = (...sources) => Object.assign({},...sources);
(5)为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。
注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。
声明对象的时候,为了防止,声明的对象被覆盖,一般用const声明。
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
}
}
processContent({url:{port: 8080}})
//{ url: {port: 8000}}
上面代码的愿意是将url.port改成8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。
我的理解是,非嵌套类型,假如你这里说的是值类型。
值类型放在堆栈中,是只会存在一份的,谁用归谁。
引用类型,放在堆中,这时候,浅拷贝是拷贝了一份引用放在堆栈,深拷贝是拷贝了对象本身放在堆。
如何实现一个对象的深拷贝?
http://www.jb51.net/article/99013.htm
Object的常用方法和属性也是很常用的。
一、属性
Object自带一个prototype的属性,即Object.prototype, Object.prototype本身也只是一个对象,也会有一些属性和方法。
1.属性
Object.prototype.writable:默认为false
Object.prototype.enumerable:默认为false
Object.prototype.configurable:默认为false
Object.prototype.constructor:用于创建一个对象的原型。
2、常用方法
Object.prototype.hasOwnProperty():返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承。
Object.prototype.isPrototypeOf():返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable():判断指定属性是否可枚举。
Object.prototype.toString():返回对象的字符串表示。
Object.prototype.watch():给对象的某个属性增加监听。
Object.prototype.unwatch():移除对象某个属性的监听。
Object.prototype.valueOf():返回指定对象的原始值。
二、方法
Object.assign(target,...sources):把任意多的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
Object.create(proto,[propertiesobject]):创建一个拥有指定原型和若干个指定属性的对象。
Object.defineProperty(obj,prop,descriptor):直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。obj:需要定义属性的对象。prop:需定义或修改的属性的名字。descriptor:将被定义或修改的属性的描述符。
Object.entries(obj):返回一个包含由给定对象所有可枚举属性的属性名和属性值组成的 [属性名,属性值] 键值对的数组,数组中键值对的排列顺序和使用for…in循环遍历该对象时返回的顺序一致。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.entries(obj)); // [ [‘foo’, ‘bar’], [‘baz’, 42] ]
Object.freeze(obj):冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
Object.getOwnPropertyNames(obj):返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组。
举例:
// 类数组对象
var obj = { 0: “a”, 1: “b”, 2: “c”};
console.log(Object.getOwnPropertyNames(obj).sort()); // [“0”, “1”, “2”]
Object.getPrototypeOf(object):返回该对象的原型。
Object.is(value1, value2):判断两个值是否是同一个值。
Object.isExtensible(obj):判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.isFrozen(obj):判断一个对象是否被冻结(frozen)。
Object.isSealed(obj):判断一个对象是否是密封的(sealed)。密封对象是指那些不可 扩展 的,且所有自身属性都不可配置的(non-configurable)且属性不可删除的对象(其可以是可写的)。
Object.keys(obj):返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致
举例:
var arr = [“a”, “b”, “c”];
alert(Object.keys(arr)); // 弹出”0,1,2”
// 类数组对象
var obj = { 0 : “a”, 1 : “b”, 2 : “c”};
alert(Object.keys(obj)); // 弹出”0,1,2”
Object.preventExtensions(obj):让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.setPrototypeOf(obj, prototype):将一个指定的对象的原型设置为另一个对象或者null
Object.values(obj):返回一个包含指定对象所有的可枚举属性值的数组,数组中的值顺序和使用for…in循环遍历的顺序一样。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.values(obj)); // [‘bar’, 42]
Object.assign()与深拷贝
深拷贝与浅拷贝
所谓深拷贝与浅拷贝,是围绕引用类型变量的拷贝进行的讨论。
在ecmascript中,变量分为基本类型和引用类型两种。其本质区别是不可变性,基本类型是不可变的,而引用类型是可变的。
所谓基本类型的不可变性,我们可以举个例子
let a = 1;
let b = 1;
a++;
a===2;//true
b===1;//true
声明一个变量a,并为其赋值1,这时内存中开辟出一片区域用来储存1。此时声明了一个变量b,也为b赋值1。当执行a++时,
基本类型的不可变性就体现出来,a++的值应该为2,但是这个值并不会将原来储存1的那片内存覆盖掉,而是再开辟一片内
存来存储2。所以对于这个1来讲,他是永远不可变的。
而对于引用变量则不同,因为其存储的是只想某个内存区域的地址,所以其修改时直接操作在内存上的,
这就导致了深拷贝和浅拷贝问题的出现。
浅拷贝
let foo = {
x: 1,
y: -1
}
let bar = foo;
foo.x++;
foo.x ===2 //true
bar.x ===2 //true
这就是最简单的浅拷贝,其效果十分明显,对拷贝源的操作,会直接体现在拷贝目标上,因为这个赋值行为的本质是内存地址的赋值,
所以他们指向了同一片内存区域。
浅拷贝十分容易,也十分常见,但却无法满足需求,加入我们需要获得拷贝源完全相同,却又不会互相影响的对象,应该怎么办呢
Object.assign()
ES6为我们提供了一种十分好用的方法,Object.assign(target, ...source)方法
assign方法接受多个参数,第一个参数target为拷贝目标,剩余参数...source是拷贝源。此方法可以将...source中的属性复制到target中,
同名属性会进行覆盖,并且在复制过程中实现了'伪'深拷贝
let foo = {
a: 1,
b: 2,
c: {
d: 1
}
}
let bar = { };
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
乍一看,好像已经实现了深拷贝的效果,对foo.a进行的操作并没有体现在bar.a中,但是再往后看
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()的拷贝类型十分明显了,这是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝.
如果真的想进行深拷贝,最简单粗暴地方式就是JSON操作。
JSON对象中包含两个方法,stringify( )和parse( ),前者可以将对象JSON化,而后者可以将JSON格式转换为对象,这是一种可以实现深拷贝的方法。
但这种方法的缺陷是会破坏原型链,并且无法拷贝属性值为function的属性
所以如果只是想单纯复制一个嵌套对象,可以使用此方法
let foo = {
a:1,
b: {
c: 1
}
}
let bar = JSON.parse(JSON.stringify(foo));
来源:https://segmentfault.com/a/1190000010661297
es6入门
Object.assign方法用于对象的合并
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一个参数,Object.assign会直接返回该参数。
const obj = {a:1};
Object.assign(obj) === obj //true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举属性。
Object.assign({b: 'c'},
Object.defineProperty({},'invisible',{
enumerable: false,
value: 'hello'
})
}
//{b: 'c'}
注意点
(1)浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({},obj1);
obj1.a.b = 2;
obj2.a.b //2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
source1.b={m:6}
Object.assign(target,source1,source2)
target ={a: 1, b:{m:6}, c: 3}
target.b.m = 8; //修改m的值
source1.b.m //8 说明是浅拷贝
原因分析:string、boolean、number是按值传递的,array、obj是按引用传递。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。
这通常不是开发者想要的,需要特别小心。
一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。
jquery的$.extend(true,{})可以进行深层合并
常见用途
Object.assign方法有很多用处
(1)为对象添加属性
class Point {
constructor (x,y) {
Object.assign(this,{x,y});
}
}
const source1 = {b:2};
const source2 = {c:3};
(2)为对象添加方法
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2) {
},
anotherMethod( ) {
}
})
//等同于
SomeClass.prototype.someMethod = function (arg1,arg2) {
};
SomeClass.prototype.anotherMethod = function ( ) {
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,
再使用assign方法添加到SomeClass.prototype之中。
(3)克隆对象
function clone(origin) {
return Object.assign({},origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,用这种方法克隆,只能克隆原始对象自身的值(也就是浅拷贝),不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}
(4)合并多个对象
将多个对象合并到某个对象。
const merge = (target,...sources) => Object.assign(target,...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge = (...sources) => Object.assign({},...sources);
(5)为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。
注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。
声明对象的时候,为了防止,声明的对象被覆盖,一般用const声明。
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
}
}
processContent({url:{port: 8080}})
//{ url: {port: 8000}}
上面代码的愿意是将url.port改成8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。
我的理解是,非嵌套类型,假如你这里说的是值类型。
值类型放在堆栈中,是只会存在一份的,谁用归谁。
引用类型,放在堆中,这时候,浅拷贝是拷贝了一份引用放在堆栈,深拷贝是拷贝了对象本身放在堆。
如何实现一个对象的深拷贝?
http://www.jb51.net/article/99013.htm
收起阅读 »
分享一个带登录的 demo,解决了双webview导航启动时,首页加载卡顿的问题
研究了好久的首页启动白屏卡顿现象,虽然只有那么短短的几毫秒,但是看着总是不舒服,尝试了官方推荐的nativeTab_demo。
但是不想用那个小凸点的半圆球,改了好久都没有解决问题,无奈还是选用了这个,然后就在官方的代码上继续修改,终于找到突破口,略微修改了一下代码,实现了启动的时候 加载首页可以像nativeTab_demo那么迅速显示,不卡顿,而且可以少加载一个页面,直接加载的首页可以显示,不用再定义main页面。
所以也想分享给各位在坑中挣扎的兄弟们。这个是我准备给公司开发的APP用的,就直接分享基础版的东西给你们吧。代码写的不好,大神勿喷。
demo在下面,自己下载吧!
mui.init();
/**此处是有四个菜单,首页不需要在这里创建,默认访问的首页会自动创建出来,所以tuan.html算第二个菜单开始**/
var subPages = ['tuan.html','small.html','person.html'];
mui.plusReady(function(){
//创建主体和子页面
var sub0 = plus.webview.getWebviewById(subPages[0]);
var sub1 = plus.webview.getWebviewById(subPages[1]);
var sub2 = plus.webview.getWebviewById(subPages[2]);
/**此处保证页面只有第一次运行的时候去创建那几个子页面**/
if(!sub0 || !sub1 || !sub2){
var self = plus.webview.currentWebview();
for(var i=0;i<3;i++){
var sub = plus.webview.create(
subPages[i],
subPages[i],
{
top:'0px',
bottom:'51px'
});
}
/**此处不写hide()方法也是可以的,运行时,其他页面创建出来后都在首页后面被遮住了好像,具体我也没懂**/
}
mui(".mui-bar-tab").on("tap","a",function(e){
var tagPage = this.getAttribute("href");
/**此处如果是点击首页,就隐藏其他页面,就会显示首页了**/
if(tagPage == 'index.html'){
for(var j=0;j<3;j++){
plus.webview.getWebviewById(subPages[j]).hide();
}
}else{
plus.webview.show(tagPage,"fade-in",300);
}
})
});
写的不好,多多指教!还有一个问题,双webview模式去加载的时候,每次点击列表进入详情页面的时候会执行两次 id 传值,是因为加载了两次 mui.js 这个问题我还没有解决,有解决办法的可以在下面留言给我,谢谢!
demo在下面,自己下载吧!
研究了好久的首页启动白屏卡顿现象,虽然只有那么短短的几毫秒,但是看着总是不舒服,尝试了官方推荐的nativeTab_demo。
但是不想用那个小凸点的半圆球,改了好久都没有解决问题,无奈还是选用了这个,然后就在官方的代码上继续修改,终于找到突破口,略微修改了一下代码,实现了启动的时候 加载首页可以像nativeTab_demo那么迅速显示,不卡顿,而且可以少加载一个页面,直接加载的首页可以显示,不用再定义main页面。
所以也想分享给各位在坑中挣扎的兄弟们。这个是我准备给公司开发的APP用的,就直接分享基础版的东西给你们吧。代码写的不好,大神勿喷。
demo在下面,自己下载吧!
mui.init();
/**此处是有四个菜单,首页不需要在这里创建,默认访问的首页会自动创建出来,所以tuan.html算第二个菜单开始**/
var subPages = ['tuan.html','small.html','person.html'];
mui.plusReady(function(){
//创建主体和子页面
var sub0 = plus.webview.getWebviewById(subPages[0]);
var sub1 = plus.webview.getWebviewById(subPages[1]);
var sub2 = plus.webview.getWebviewById(subPages[2]);
/**此处保证页面只有第一次运行的时候去创建那几个子页面**/
if(!sub0 || !sub1 || !sub2){
var self = plus.webview.currentWebview();
for(var i=0;i<3;i++){
var sub = plus.webview.create(
subPages[i],
subPages[i],
{
top:'0px',
bottom:'51px'
});
}
/**此处不写hide()方法也是可以的,运行时,其他页面创建出来后都在首页后面被遮住了好像,具体我也没懂**/
}
mui(".mui-bar-tab").on("tap","a",function(e){
var tagPage = this.getAttribute("href");
/**此处如果是点击首页,就隐藏其他页面,就会显示首页了**/
if(tagPage == 'index.html'){
for(var j=0;j<3;j++){
plus.webview.getWebviewById(subPages[j]).hide();
}
}else{
plus.webview.show(tagPage,"fade-in",300);
}
})
});
写的不好,多多指教!还有一个问题,双webview模式去加载的时候,每次点击列表进入详情页面的时候会执行两次 id 传值,是因为加载了两次 mui.js 这个问题我还没有解决,有解决办法的可以在下面留言给我,谢谢!
demo在下面,自己下载吧!
收起阅读 »
调用QQAPP进行聊天
// str 为qq号码
function(str){
if(plus.os.name == "Android") {
var openUrl = 'mqqwpa://im/chat?chat_type=wpa&uin=' + str;
plus.nativeUI.showWaiting();
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass('android.content.Intent');
var Uri = plus.android.importClass('android.net.Uri');
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(openUrl));
main.startActivity(intent);
}
if(plus.os.name == "iOS") {
var openUrl = 'mqq://im/chat?chat_type=wpa&uin=' + str + '&version=1&src_type=web';
plus.runtime.launchApplication({
action: openUrl
}, function(e) {
plus.nativeUI.confirm("检查到您未安装qq,请先到appstore搜索下载?", function(i) {
if(i.index == 0) {
iosAppstore("itunes.apple.com/cn/app/mqq/");
}
});
});
}
}
// str 为qq号码
function(str){
if(plus.os.name == "Android") {
var openUrl = 'mqqwpa://im/chat?chat_type=wpa&uin=' + str;
plus.nativeUI.showWaiting();
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass('android.content.Intent');
var Uri = plus.android.importClass('android.net.Uri');
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(openUrl));
main.startActivity(intent);
}
if(plus.os.name == "iOS") {
var openUrl = 'mqq://im/chat?chat_type=wpa&uin=' + str + '&version=1&src_type=web';
plus.runtime.launchApplication({
action: openUrl
}, function(e) {
plus.nativeUI.confirm("检查到您未安装qq,请先到appstore搜索下载?", function(i) {
if(i.index == 0) {
iosAppstore("itunes.apple.com/cn/app/mqq/");
}
});
});
}
}
收起阅读 »