HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

IOS - 监听通话状态

Native.JS iOS

还是群里的网友让我帮忙翻译。。
Object-c代码如下

CTCallCenter *center = [[CTCallCenter alloc] init];  
center_ = center;  
center.callEventHandler = ^(CTCall *call){  
  NSLog(@"call:%@",call.description);  
}

Njs代码如下

var CTCall = plus.ios.importClass('CTCall');  
var CTCallCenter = plus.ios.importClass('CTCallCenter');  
var center = new CTCallCenter();  
center.init()  
center.setCallEventHandler(function(ctCall){  
    console.log(ctCall)  
})

由于我的safari抽风,没法进一步研究这个ctCall是啥,所以到这里结束了
经过网友测试有效可用,代码没有优化,偷懒直接importClass,有意思的伙伴自己用invoke处理即可。

顺带自己的Github项目,一个前端跨平台方法兼容库
Sh.js github地址
正在完善帮助文档。

继续阅读 »

还是群里的网友让我帮忙翻译。。
Object-c代码如下

CTCallCenter *center = [[CTCallCenter alloc] init];  
center_ = center;  
center.callEventHandler = ^(CTCall *call){  
  NSLog(@"call:%@",call.description);  
}

Njs代码如下

var CTCall = plus.ios.importClass('CTCall');  
var CTCallCenter = plus.ios.importClass('CTCallCenter');  
var center = new CTCallCenter();  
center.init()  
center.setCallEventHandler(function(ctCall){  
    console.log(ctCall)  
})

由于我的safari抽风,没法进一步研究这个ctCall是啥,所以到这里结束了
经过网友测试有效可用,代码没有优化,偷懒直接importClass,有意思的伙伴自己用invoke处理即可。

顺带自己的Github项目,一个前端跨平台方法兼容库
Sh.js github地址
正在完善帮助文档。

收起阅读 »

IOS - 原生获取IDFV IDFA

iOS Native.JS

群里面一个朋友让我帮忙翻译的
Object-c代码如下:

NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

注:下面的IDFA等同于adid

    console.log('UUID:'+plus.device.uuid)  
    console.log('important')  
    var NSUUID = plus.ios.importClass('NSUUID');  
    var UIDevice = plus.ios.importClass("UIDevice");  
    var currentDevice = UIDevice.currentDevice()  
    var identifierForVendor = currentDevice.identifierForVendor().UUIDString();  
    console.log('IDFV:'+identifierForVendor)  

    var ASIdentifierManager = plus.ios.importClass('ASIdentifierManager');  
    var sharedManager = ASIdentifierManager.sharedManager();  
    var IDFA = sharedManager.advertisingIdentifier().UUIDString();  
    console.log('IDFA:'+IDFA);

经过网友测试有效可用,代码没有优化,偷懒直接importClass,有意思的伙伴自己用invoke处理即可。

转载本文记得备注出处,谢谢。

继续阅读 »

群里面一个朋友让我帮忙翻译的
Object-c代码如下:

NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

注:下面的IDFA等同于adid

    console.log('UUID:'+plus.device.uuid)  
    console.log('important')  
    var NSUUID = plus.ios.importClass('NSUUID');  
    var UIDevice = plus.ios.importClass("UIDevice");  
    var currentDevice = UIDevice.currentDevice()  
    var identifierForVendor = currentDevice.identifierForVendor().UUIDString();  
    console.log('IDFV:'+identifierForVendor)  

    var ASIdentifierManager = plus.ios.importClass('ASIdentifierManager');  
    var sharedManager = ASIdentifierManager.sharedManager();  
    var IDFA = sharedManager.advertisingIdentifier().UUIDString();  
    console.log('IDFA:'+IDFA);

经过网友测试有效可用,代码没有优化,偷懒直接importClass,有意思的伙伴自己用invoke处理即可。

转载本文记得备注出处,谢谢。

收起阅读 »

理解Object.defineProperty()

对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。
定义对象可以使用构造函数或字面量的形式:

var obj = new Object; //obj = {}
obj.name = "张三"; //添加描述
obj.say = function(){}; //添加行为
除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性或修改原有的属性。

Object.defineProperty()
语法:

Object.defineProperty(obj, prop, descriptor)
参数说明:

obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性
返回值:

传入函数的对象。即第一个参数obj
针对属性,我们可以给这个属性设置一些特性,比如是否只读不可以写;是否可以被for..in或Object.keys()遍历。

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

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

var obj = {
test:"hello"
}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
//对象新添加的属性的特性描述
Object.defineProperty(obj,"newKey",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
数据描述中的属性都是可选的,来看一下设置每一个属性的作用。

value

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

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

});
console.log( obj.newKey ); //undefined


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

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

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

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

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

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

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

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

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

这个属性起到两个作用:

目标属性是否可以使用delete删除
目标属性是否可以再次设置特性
//-----------------测试目标属性是否能被删除------------------------
var obj = {}
//第一种情况:configurable设置为false,不能被删除。
Object.defineProperty(obj,"newKey",{
value:"hello",
writable:false,
enumerable:false,
configurable:false
});
//删除属性
delete obj.newKey;
console.log( obj.newKey ); //hello

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

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

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

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

//重新修改特性
Object.defineProperty(obj,"newKey",{
value:"hello",
writable:true,
enumerable:true,
configurable:true
});
console.log( obj.newKey ); //hello
除了可以给新定义的属性设置特性,也可以给已有的属性设置特性

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

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

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

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

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

console.log( obj.test ); //依然是:'change value'
提示:一旦使用Object.defineProperty给对象添加属性,那么如果不设置属性的特性,那么configurable、enumerable、writable这些值都为默认的false

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

});

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

//枚举
for( var attr in obj ){
console.log(attr);
}
设置的特性总结:

value: 设置属性的值
writable: 值是否可以重写。true | false
enumerable: 目标属性是否可以被枚举。true | false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
存取器描述
当使用存取器描述属性的特性的时候,允许设置以下特性属性:

var obj = {};
Object.defineProperty(obj,"newKey",{
get:function (){} | undefined,
set:function (value){} | undefined
configurable: true | false
enumerable: true | false
});
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性

getter/setter

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

getter 是一种获得属性值的方法
setter是一种设置属性值的方法。
在特性中使用get/set属性来定义对应的方法。

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

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

console.log( obj.newKey ); //change value
注意:get或set不是必须成对出现,任写其一就可以。如果不设置方法,则get和set的默认值为undefined

configurable和enumerable同上面的用法。

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

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

来源: https://segmentfault.com/a/1190000007434923

继续阅读 »

对象是由多个名/值对组成的无序的集合。对象中每个属性对应任意类型的值。
定义对象可以使用构造函数或字面量的形式:

var obj = new Object; //obj = {}
obj.name = "张三"; //添加描述
obj.say = function(){}; //添加行为
除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性或修改原有的属性。

Object.defineProperty()
语法:

Object.defineProperty(obj, prop, descriptor)
参数说明:

obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性
返回值:

传入函数的对象。即第一个参数obj
针对属性,我们可以给这个属性设置一些特性,比如是否只读不可以写;是否可以被for..in或Object.keys()遍历。

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

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

var obj = {
test:"hello"
}
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
//对象新添加的属性的特性描述
Object.defineProperty(obj,"newKey",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
数据描述中的属性都是可选的,来看一下设置每一个属性的作用。

value

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

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

});
console.log( obj.newKey ); //undefined


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

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

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

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

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

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

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

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

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

这个属性起到两个作用:

目标属性是否可以使用delete删除
目标属性是否可以再次设置特性
//-----------------测试目标属性是否能被删除------------------------
var obj = {}
//第一种情况:configurable设置为false,不能被删除。
Object.defineProperty(obj,"newKey",{
value:"hello",
writable:false,
enumerable:false,
configurable:false
});
//删除属性
delete obj.newKey;
console.log( obj.newKey ); //hello

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

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

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

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

//重新修改特性
Object.defineProperty(obj,"newKey",{
value:"hello",
writable:true,
enumerable:true,
configurable:true
});
console.log( obj.newKey ); //hello
除了可以给新定义的属性设置特性,也可以给已有的属性设置特性

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

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

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

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

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

console.log( obj.test ); //依然是:'change value'
提示:一旦使用Object.defineProperty给对象添加属性,那么如果不设置属性的特性,那么configurable、enumerable、writable这些值都为默认的false

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

});

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

//枚举
for( var attr in obj ){
console.log(attr);
}
设置的特性总结:

value: 设置属性的值
writable: 值是否可以重写。true | false
enumerable: 目标属性是否可以被枚举。true | false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
存取器描述
当使用存取器描述属性的特性的时候,允许设置以下特性属性:

var obj = {};
Object.defineProperty(obj,"newKey",{
get:function (){} | undefined,
set:function (value){} | undefined
configurable: true | false
enumerable: true | false
});
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性

getter/setter

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

getter 是一种获得属性值的方法
setter是一种设置属性值的方法。
在特性中使用get/set属性来定义对应的方法。

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

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

console.log( obj.newKey ); //change value
注意:get或set不是必须成对出现,任写其一就可以。如果不设置方法,则get和set的默认值为undefined

configurable和enumerable同上面的用法。

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

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

来源: https://segmentfault.com/a/1190000007434923

收起阅读 »

js去掉小数点后多余的0

js

可以用parseFloat()
例如:

var s = 163.0055300;
var m = parseFloat(s); //163.00553

可以用parseFloat()
例如:

var s = 163.0055300;
var m = parseFloat(s); //163.00553

深入浅出妙用js中的apply call bind

js

在js中,call和apply都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部this的指向
js的一大特点是,函数存在【定义时上下文】和【运行时上下文】以及【上下文是可以改变的】这样的概念。

比如:

function fruits() { };  

}  
fruits.prototype = {  
        color: "red",  
        say: function () {  
            console.log("My color is" + htis.color);  
       }  
 var apple = new fruits;  
 apple.say();

但是如果我们有一个对象banana = {color: "yellow"},我们不想对它重新定义say方法,那么我们可以通过call或apply用blackCat的say方法:blackCat.say.call(whiteDog);
所以,可以看出call和apply是为了动态改变this而出现的,当一个object没有某个方法,但是其他的有,我们可以借助call或apply用其他对象的方法来操作。
用的比较多的,通过document.getElementsByTayName选择的dom节点是一重类似array的array。它不能应用Array下的push.pop等方法。我们可以通过:

var domNodes = Array.protorype.slice.call(document.getElementByTagName("*"));

这样domNodes就可以应用Array下的所有方法了。

apply的使用
获取数组中的最大值

var arr = [56,32,22,6];  
var max = Math.max.apply(Math,arr);  
console.log(max); //56

apply( )和call( )都是在特定的作用域中调用函数

function sum(num1,num2){  
      return num1 + num2;  
}  
function callSum1 (num1 , num2) {  
     return sum.apply(this ,arguments);  
}  
function callSum2 (num1, num2) {  
    return sum.apply(this, [num1,num2]);  
}  
alert(callSum1(10,10));//20  
alert(callSum2(10,10));//20

apply()和call()真正的用武之地,他们真正强大的地方是能够扩充函数赖以运行的作用域。

window.color = "red";  
var o = {color: "blue"};  
function sayColor() {  
       alert(this.color);  
}  
sayColor( ); //red  
sayColor.call(this);//red  
sayColor.call(window);//red  
sayColor.call(o);//blue   将sayColor的this指向o

使用call()(或apply())来扩充作用域最大的好处,就是对象不需要与方法有任何耦合关系。

让伪数组使用数组的方法(伪数组:长得很像数组,但是没有数组的方法)

var obj = {  
    0:8,  
    1:7,  
    2:9,  
    length:3  
}  
delete obj.0//数字不能作为变量名的开头,这种方法会报错  
delete obj.['0']   
//删除数组中的某几项元素  
Array.prototype.splice.call(obj,0,2);//从0开始删,删除两项,序号也变化  
console.log(obj);  
Array.prototype.push.call(obj,15);//length也+1  

var o = {  
    name:'zs'  
}  
delete o.name;  

call,bind,apply一般用来指定this环境

 var a = {  
          user: "小丸子",  
          fn: function () {  
            console.log(this.user);  
          }  
    }  
a.fn();//小丸子  
 var b = a.fn;  
 b( ); //undefined

1.call(a) 通过在call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象。

b.call(a); //将b的this指向a

注意:如果call和apply的第一个参数写的是null,那么this指向的是window对象
bind和call、apply方法有些不同,但不管怎么说他们都可以用来改变this的指向。
bind方法返回的是一个修改过后的函数。
bind可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

var m = {  
    user:"小丸子",  
    fn: function(e,d,f) {  
           console.log(this.user);//小丸子  
            console.log(e,d,f);//10 1 2  
        }  
    }  
var n = m.fn;  
var z = n.bind(10);  
z(1,2);

call()和apply()就是改变函数的执行上下文,也就是this值。他们两个是Function对象的方法,每个函数都能调用
。他们的第一个参数就是你要指定的执行的执行上下文。说白了,就是调用函数,但是让它在你指定的上下文下执行,
这样,函数可以访问的作用域就会改变。下面看点代码:

function apply1(num1,num2){  
     return sum.apply(this,[num1,num2]);  
}  
tunction call1(num1,num2){  
     return sum.call(this,num1,num2);  
}

这里,我们执行环境传的是this,也就是没改变函数的执行上下文。他们两个的参数都可以传arguments。
bind()是es5中的方法,bind是新创建一个函数,然后把它的上下文绑定到bind()括号中。所以bind后函数不会执行,
而只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数。

var button = document.getElementById("button"),  
     text = document.getElementById("text");  
button.onclick = function ( ) {  
      alert(this.id);//弹出text  
}.bind(text); 

但由于ie6~ie8不支持该方法,所以,若想在这几个浏览器中使用,就要模拟该方法,模拟的代码如下:

if(!function ( ) { }.bind) {   
    Function.prototype.bind = function(context) {  
          var self = this,args = Array.prototype.slick.call(arguments);  
          return function() {  
                    return self.apply(context,args.slice(1));  
         }  
   }  
}

首先,判断是否存在bind方法,然后,若不存在,向Function对象的原型中添加自定义的bind方法。
这里面var self = this让我很困扰,按理说,prototype是一个对象,对象的this应该指向对象本身,也就是prototye,但真的是这样吗。看如下代码:

function a(){};  
a.prototype.testThis = function(){console.log(a.prototype == this;)}  
var b = new a();  
b.testThis();//false

显然,this不指向prototype,而经过测试,它也不指向a,而指向b。所以原型中this值就明朗了。指向调用它的对象。

Array.prototype.slice.call(arguments);

接下来就是上面这段代码,它会将一个类数组形式的变量转化为真正的数组。查slice的用法。不要弄混(由于arguments自己没有sllice方法,这里属于借用Array原型的slice方法)。而且经过测试,如果不给slice传参数,就等于传了个0给它,结果就是返回一个和原来数组一模一样的副本。
之后的代码就很好理解,返回一个函数,该函数把传给bind的第一个参数当做执行上下文,由于args已经是一个数组,排除第一项,将之后的部分作为第二部分参数传给apply。如此,我们自己的这个bind函数的行为就同es5中的bind一样了。

//数组之间追加  
var array1 = [12,"foo",{name:"joe"},-1232];  
var array2 = ["Doe",555,100];  
Array.prototype.push.apply(array1,array2);  
/* array1 值为  [12 , "foo" , {name "Joe"} , -1232 , "Doe" , 555 , 100] */  
//获取数组中的最大值和最小值  
var numbers = [5,36,189,-215];  
var maxInNumbers = Math.max.apply(Math,numbers), //189  
maxInNumbers = Math.max.call(Math,5,36,189,-215);  
//number本身没有max方法,但是Math有,我们就可以借助call或者apply使用其方法。  
//验证是否是数组(前提是toString()方法没有被重写过)  
function isArray(obj){  
     return Object.prototype.toString.call(obj) === '[object Array]';  
}  
类(伪)数组使用数组方法  
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

JavaScript中存在一种名为伪数组的对象结构。比较特别的是arguments对象,还有像调用getElementsByTayName,document.childNodes之类的,它们返回NodeList对象都属于伪数组。不能应用Array下的push,pop等方法。
但是我们能通过Array.prototype.slice.call转换为真正的数组的带有length属性的对象,这样domNodes就可以应用Array下的所有方法了。

定义一个log方法,让它可以代理console.log方法,常见的解决方法是:

function log(msg) {  
      console.log(msg);  
}  
log(1);// 1  
log(1,2);//1

当传入的参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用apply或者call,注意;这里传入多少个参数是不确定的,所以使用apply最好的,方法如下:

function log( ){  
     console.log.apply(console,arguments);  
};  
log(1); //1  
log(1,2); //1 2

接下来的要求是给每一个log消息添加一个“(app)”的前缀,比如:
log(“hello world”); //(app)hello world
该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过Array.prototype.slice.call转化为标准数组,再使用数组方法unshift,像这样

    function log() {  
        //先将传过来的参数转为数组  
          var args = Array.prototype.slice.call(arguments);  
          args.unshift('(app)');  
          console.log.apply(console,args);  
    }

bind 详解
bind()方法与apply和call很相似,也是可以改变函数体内this的指向。
MDN的解释是:bind()方法会创建一个新函数,成为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

在常见的单例模式中,通常我们会使用_this,that,self等保存this,这样我们就可以在改变了上下文之后继续引用到它。例如:

var foo = {  
           bar: 1,  
           eventBind: function () {  
              var _this = this;  
              $('.someClass').on('click',function(event){  
                console.log(_this.bar);  
              });  
           }  
    }

由于JavaScript特有的机制,上下文环境在 eventBind:function(){ }过渡到$(".someClass").on("click",function(event){ })发生了改变,上述使用变量保存this这些方式都是有用的,也没有什么问题。当然使用bind()可以更加优雅的解决这个问题:

    var foo = {  
            bar: 1,  
            eventBind: function(){  
                    $('.someClass').on('click',function(event){  
                           console.log(this.bar);  
                    }.bind(this));  
            }    
    }

在上述代码里,bind()创建了一个函数,当这个click事件绑定在被调用的时候,它的this关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,这里我们传入想要的上下文this(就是foo),到bind()函数中。然后,当回调函数被执行的时候,this便指向foo对象。

var bar = function(){  
   console.log(this.x);  
}  
var foo = {  
   x:3  
}  
bar();//undefined  
var func = bar.bind(foo);  
func( );//3

这里我们创建了一个新的函数func,当使用bind()创建一个绑定函数之后,它被执行的时候,它的this会被设置成foo,而不是像我们调用bar()时 的全局作用域。
有个有趣的问题,如果连续bind()两次,亦或连续bind()多次,输出的值是什么呢?

var bar = function ( ){  
      console.log(this.x);  
}  
var foo = {  
     x:3  
}  
var sed = {  
    x:4  
}  
var fund = bar.bind(foo).bind(sed);  
fun( );  
var fiv = {  
    x:5  
}  
var fund = bar.bind(foo).bind(sed).bind(fiv);  
fun( );//

答案是,两次都仍将输出3,而非期待中的4和5,原因是在JavaScript中,多次bind()是无效的。更深层次的原因,bind()的实现,相当于使用函数在内部包了一个call/apply,第二次bind( )相当于再包住第一次bind(),故第二次以后的bind是无法生效的。

var obj = {  
   x:81  
}  
var foo = {  
   getX: function( ) {  
       return this.x;  
   }  
}  
console.log(foo.getX.bind(obj)( ));//81  
console.log(foo.call(obj));//81  
console.log(foo.getX.apply(obj)); // 81

apply、call、bind的第一个参数都是将当前函数的this指向现在你想要使用的this上,就可以调用它的方法了
注意:bind()方法后多了对括号。
当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用bind()方法,而apply/call则会立即执行函数。
总结:
(1)apply、call、bind三者都是用来改变函数的this对象的指向的;
(2)apply、call、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文;
(3)apply、call、bind三者都可以利用后续参数传参;
(4)bind是返回对应函数,便于稍后调用;apply、call则是立即调用。

继续阅读 »

在js中,call和apply都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部this的指向
js的一大特点是,函数存在【定义时上下文】和【运行时上下文】以及【上下文是可以改变的】这样的概念。

比如:

function fruits() { };  

}  
fruits.prototype = {  
        color: "red",  
        say: function () {  
            console.log("My color is" + htis.color);  
       }  
 var apple = new fruits;  
 apple.say();

但是如果我们有一个对象banana = {color: "yellow"},我们不想对它重新定义say方法,那么我们可以通过call或apply用blackCat的say方法:blackCat.say.call(whiteDog);
所以,可以看出call和apply是为了动态改变this而出现的,当一个object没有某个方法,但是其他的有,我们可以借助call或apply用其他对象的方法来操作。
用的比较多的,通过document.getElementsByTayName选择的dom节点是一重类似array的array。它不能应用Array下的push.pop等方法。我们可以通过:

var domNodes = Array.protorype.slice.call(document.getElementByTagName("*"));

这样domNodes就可以应用Array下的所有方法了。

apply的使用
获取数组中的最大值

var arr = [56,32,22,6];  
var max = Math.max.apply(Math,arr);  
console.log(max); //56

apply( )和call( )都是在特定的作用域中调用函数

function sum(num1,num2){  
      return num1 + num2;  
}  
function callSum1 (num1 , num2) {  
     return sum.apply(this ,arguments);  
}  
function callSum2 (num1, num2) {  
    return sum.apply(this, [num1,num2]);  
}  
alert(callSum1(10,10));//20  
alert(callSum2(10,10));//20

apply()和call()真正的用武之地,他们真正强大的地方是能够扩充函数赖以运行的作用域。

window.color = "red";  
var o = {color: "blue"};  
function sayColor() {  
       alert(this.color);  
}  
sayColor( ); //red  
sayColor.call(this);//red  
sayColor.call(window);//red  
sayColor.call(o);//blue   将sayColor的this指向o

使用call()(或apply())来扩充作用域最大的好处,就是对象不需要与方法有任何耦合关系。

让伪数组使用数组的方法(伪数组:长得很像数组,但是没有数组的方法)

var obj = {  
    0:8,  
    1:7,  
    2:9,  
    length:3  
}  
delete obj.0//数字不能作为变量名的开头,这种方法会报错  
delete obj.['0']   
//删除数组中的某几项元素  
Array.prototype.splice.call(obj,0,2);//从0开始删,删除两项,序号也变化  
console.log(obj);  
Array.prototype.push.call(obj,15);//length也+1  

var o = {  
    name:'zs'  
}  
delete o.name;  

call,bind,apply一般用来指定this环境

 var a = {  
          user: "小丸子",  
          fn: function () {  
            console.log(this.user);  
          }  
    }  
a.fn();//小丸子  
 var b = a.fn;  
 b( ); //undefined

1.call(a) 通过在call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象。

b.call(a); //将b的this指向a

注意:如果call和apply的第一个参数写的是null,那么this指向的是window对象
bind和call、apply方法有些不同,但不管怎么说他们都可以用来改变this的指向。
bind方法返回的是一个修改过后的函数。
bind可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。

var m = {  
    user:"小丸子",  
    fn: function(e,d,f) {  
           console.log(this.user);//小丸子  
            console.log(e,d,f);//10 1 2  
        }  
    }  
var n = m.fn;  
var z = n.bind(10);  
z(1,2);

call()和apply()就是改变函数的执行上下文,也就是this值。他们两个是Function对象的方法,每个函数都能调用
。他们的第一个参数就是你要指定的执行的执行上下文。说白了,就是调用函数,但是让它在你指定的上下文下执行,
这样,函数可以访问的作用域就会改变。下面看点代码:

function apply1(num1,num2){  
     return sum.apply(this,[num1,num2]);  
}  
tunction call1(num1,num2){  
     return sum.call(this,num1,num2);  
}

这里,我们执行环境传的是this,也就是没改变函数的执行上下文。他们两个的参数都可以传arguments。
bind()是es5中的方法,bind是新创建一个函数,然后把它的上下文绑定到bind()括号中。所以bind后函数不会执行,
而只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数。

var button = document.getElementById("button"),  
     text = document.getElementById("text");  
button.onclick = function ( ) {  
      alert(this.id);//弹出text  
}.bind(text); 

但由于ie6~ie8不支持该方法,所以,若想在这几个浏览器中使用,就要模拟该方法,模拟的代码如下:

if(!function ( ) { }.bind) {   
    Function.prototype.bind = function(context) {  
          var self = this,args = Array.prototype.slick.call(arguments);  
          return function() {  
                    return self.apply(context,args.slice(1));  
         }  
   }  
}

首先,判断是否存在bind方法,然后,若不存在,向Function对象的原型中添加自定义的bind方法。
这里面var self = this让我很困扰,按理说,prototype是一个对象,对象的this应该指向对象本身,也就是prototye,但真的是这样吗。看如下代码:

function a(){};  
a.prototype.testThis = function(){console.log(a.prototype == this;)}  
var b = new a();  
b.testThis();//false

显然,this不指向prototype,而经过测试,它也不指向a,而指向b。所以原型中this值就明朗了。指向调用它的对象。

Array.prototype.slice.call(arguments);

接下来就是上面这段代码,它会将一个类数组形式的变量转化为真正的数组。查slice的用法。不要弄混(由于arguments自己没有sllice方法,这里属于借用Array原型的slice方法)。而且经过测试,如果不给slice传参数,就等于传了个0给它,结果就是返回一个和原来数组一模一样的副本。
之后的代码就很好理解,返回一个函数,该函数把传给bind的第一个参数当做执行上下文,由于args已经是一个数组,排除第一项,将之后的部分作为第二部分参数传给apply。如此,我们自己的这个bind函数的行为就同es5中的bind一样了。

//数组之间追加  
var array1 = [12,"foo",{name:"joe"},-1232];  
var array2 = ["Doe",555,100];  
Array.prototype.push.apply(array1,array2);  
/* array1 值为  [12 , "foo" , {name "Joe"} , -1232 , "Doe" , 555 , 100] */  
//获取数组中的最大值和最小值  
var numbers = [5,36,189,-215];  
var maxInNumbers = Math.max.apply(Math,numbers), //189  
maxInNumbers = Math.max.call(Math,5,36,189,-215);  
//number本身没有max方法,但是Math有,我们就可以借助call或者apply使用其方法。  
//验证是否是数组(前提是toString()方法没有被重写过)  
function isArray(obj){  
     return Object.prototype.toString.call(obj) === '[object Array]';  
}  
类(伪)数组使用数组方法  
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

JavaScript中存在一种名为伪数组的对象结构。比较特别的是arguments对象,还有像调用getElementsByTayName,document.childNodes之类的,它们返回NodeList对象都属于伪数组。不能应用Array下的push,pop等方法。
但是我们能通过Array.prototype.slice.call转换为真正的数组的带有length属性的对象,这样domNodes就可以应用Array下的所有方法了。

定义一个log方法,让它可以代理console.log方法,常见的解决方法是:

function log(msg) {  
      console.log(msg);  
}  
log(1);// 1  
log(1,2);//1

当传入的参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用apply或者call,注意;这里传入多少个参数是不确定的,所以使用apply最好的,方法如下:

function log( ){  
     console.log.apply(console,arguments);  
};  
log(1); //1  
log(1,2); //1 2

接下来的要求是给每一个log消息添加一个“(app)”的前缀,比如:
log(“hello world”); //(app)hello world
该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过Array.prototype.slice.call转化为标准数组,再使用数组方法unshift,像这样

    function log() {  
        //先将传过来的参数转为数组  
          var args = Array.prototype.slice.call(arguments);  
          args.unshift('(app)');  
          console.log.apply(console,args);  
    }

bind 详解
bind()方法与apply和call很相似,也是可以改变函数体内this的指向。
MDN的解释是:bind()方法会创建一个新函数,成为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

在常见的单例模式中,通常我们会使用_this,that,self等保存this,这样我们就可以在改变了上下文之后继续引用到它。例如:

var foo = {  
           bar: 1,  
           eventBind: function () {  
              var _this = this;  
              $('.someClass').on('click',function(event){  
                console.log(_this.bar);  
              });  
           }  
    }

由于JavaScript特有的机制,上下文环境在 eventBind:function(){ }过渡到$(".someClass").on("click",function(event){ })发生了改变,上述使用变量保存this这些方式都是有用的,也没有什么问题。当然使用bind()可以更加优雅的解决这个问题:

    var foo = {  
            bar: 1,  
            eventBind: function(){  
                    $('.someClass').on('click',function(event){  
                           console.log(this.bar);  
                    }.bind(this));  
            }    
    }

在上述代码里,bind()创建了一个函数,当这个click事件绑定在被调用的时候,它的this关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,这里我们传入想要的上下文this(就是foo),到bind()函数中。然后,当回调函数被执行的时候,this便指向foo对象。

var bar = function(){  
   console.log(this.x);  
}  
var foo = {  
   x:3  
}  
bar();//undefined  
var func = bar.bind(foo);  
func( );//3

这里我们创建了一个新的函数func,当使用bind()创建一个绑定函数之后,它被执行的时候,它的this会被设置成foo,而不是像我们调用bar()时 的全局作用域。
有个有趣的问题,如果连续bind()两次,亦或连续bind()多次,输出的值是什么呢?

var bar = function ( ){  
      console.log(this.x);  
}  
var foo = {  
     x:3  
}  
var sed = {  
    x:4  
}  
var fund = bar.bind(foo).bind(sed);  
fun( );  
var fiv = {  
    x:5  
}  
var fund = bar.bind(foo).bind(sed).bind(fiv);  
fun( );//

答案是,两次都仍将输出3,而非期待中的4和5,原因是在JavaScript中,多次bind()是无效的。更深层次的原因,bind()的实现,相当于使用函数在内部包了一个call/apply,第二次bind( )相当于再包住第一次bind(),故第二次以后的bind是无法生效的。

var obj = {  
   x:81  
}  
var foo = {  
   getX: function( ) {  
       return this.x;  
   }  
}  
console.log(foo.getX.bind(obj)( ));//81  
console.log(foo.call(obj));//81  
console.log(foo.getX.apply(obj)); // 81

apply、call、bind的第一个参数都是将当前函数的this指向现在你想要使用的this上,就可以调用它的方法了
注意:bind()方法后多了对括号。
当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用bind()方法,而apply/call则会立即执行函数。
总结:
(1)apply、call、bind三者都是用来改变函数的this对象的指向的;
(2)apply、call、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文;
(3)apply、call、bind三者都可以利用后续参数传参;
(4)bind是返回对应函数,便于稍后调用;apply、call则是立即调用。

收起阅读 »

Http长连接和管线化 Servlet NIO

XMLHttpRequest

http长连接

HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;他的好处:  
  1,同一个客户端可以使用这个长连接处理其他求情,避免http重新链接和断开所消耗的时间。  
  2,服务器可以利用这个连接 主动推送 消息到客户端(这个才是我想要的)。  
  HTTP头部有了Keep-Alive这个值,代表客户端这次请求期望是长连接的。但是并不代表一定会使用长连接,服务器端都可以无视这个值,也就是不按标准来。这里需要后台服务器支持这个标准。  

管线化


也可以理解为流水线。在长连接的基础上,将客户端的其他请求都交给这一个连接去处理。这里服务器需要注意一件事情。那就是这个请求的先后顺序是不能颠倒的

Servlet NIO


这个是servlet3.0 以后的功能,目前已经到4.x了。   
非阻塞IO操作。   
阻塞IO的servlet 每处理一次请求需要一个线程。   
非阻塞IO操作可以在servlet的中利用ReadListener,WriteListener,可以实现非阻塞操作

来源:http://blog.csdn.net/pk_sir/article/details/76213480

继续阅读 »

http长连接

HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;他的好处:  
  1,同一个客户端可以使用这个长连接处理其他求情,避免http重新链接和断开所消耗的时间。  
  2,服务器可以利用这个连接 主动推送 消息到客户端(这个才是我想要的)。  
  HTTP头部有了Keep-Alive这个值,代表客户端这次请求期望是长连接的。但是并不代表一定会使用长连接,服务器端都可以无视这个值,也就是不按标准来。这里需要后台服务器支持这个标准。  

管线化


也可以理解为流水线。在长连接的基础上,将客户端的其他请求都交给这一个连接去处理。这里服务器需要注意一件事情。那就是这个请求的先后顺序是不能颠倒的

Servlet NIO


这个是servlet3.0 以后的功能,目前已经到4.x了。   
非阻塞IO操作。   
阻塞IO的servlet 每处理一次请求需要一个线程。   
非阻塞IO操作可以在servlet的中利用ReadListener,WriteListener,可以实现非阻塞操作

来源:http://blog.csdn.net/pk_sir/article/details/76213480

收起阅读 »

js实现深拷贝

Native.JS

type函数 首先我们要实现一个getType函数对元素进行类型判断,关于元素的类型判断, js中typeof和instanceof详解 ,这里用一个更简便的方法,直接调用Object.prototype.toString 方法。

  function getType(obj){  
       //tostring会返回对应不同的标签的构造函数  
       var toString = Object.prototype.toString;  
       var map = {  
          '[object Boolean]'  : 'boolean',   
          '[object Number]'   : 'number',   
          '[object String]'   : 'string',   
          '[object Function]' : 'function',   
          '[object Array]'    : 'array',   
          '[object Date]'     : 'date',   
          '[object RegExp]'   : 'regExp',   
          '[object Undefined]': 'undefined',  
          '[object Null]'     : 'null',   
          '[object Object]'   : 'object'  
      };  
      if(obj instanceof Element) {  
           return 'element';  
      }  
      return map[toString.call(obj)];  
   }  

深拷贝(deepClone)

对于一个引用类型,如果直接将它赋值给另一个变量,由于这两个引用指向同一个地址,这时改变其中任何一个引用,另一个都会受到影响。当我们想复制一个对象并且切断与这个对象的联系,就要使用深拷贝。对于一个对象来说,由于可能有多层结构,所以我们可以使用递归来解决这个问题

  function deepClone(data){  
       var type = getType(data);  
       var obj;  
       if(type === 'array'){  
           obj = [];  
       } else if(type === 'object'){  
           obj = {};  
       } else {  
           //不再具有下一层次  
           return data;  
       }  
       if(type === 'array'){  
           for(var i = 0, len = data.length; i < len; i++){  
               obj.push(deepClone(data[i]));  
           }  
       } else if(type === 'object'){  
           for(var key in data){  
               obj[key] = deepClone(data[key]);  
           }  
       }  
       return obj;  
   }

对于function类型,这里是直接赋值的,还是共享一个内存值。这是因为函数更多的是完成某些功能,有个输入值和返回值,而且对于上层业务而言更多的是完成业务功能,并不需要真正将函数深拷贝。

广度优先遍历

上面是使用递归来进行深拷贝,显然我们可以使用树的广度优先遍历来实现

//这里为了阅读方便,只深拷贝对象,关于数组的判断参照上面的例子

   function deepClone(data){  
       var obj = {};  
       var originQueue = [data];  
       var copyQueue = [obj];  
       //以下两个队列用来保存复制过程中访问过的对象,以此来避免对象环的问题(对象的某个属性值是对象本身)  
       var visitQueue = [];  
       var copyVisitQueue = [];  
       while(originQueue.length > 0){  
           var _data = originQueue.shift();  
           var _obj = copyQueue.shift();  
           visitQueue.push(_data);  
           copyVisitQueue.push(_obj);  
           for(var key in _data){  
               var _value = _data[key]  
               if(typeof _value !== 'object'){  
                   _obj[key] = _value;  
               } else {  
                   //使用indexOf可以发现数组中是否存在相同的对象(实现indexOf的难点就在于对象比较)  
                   var index = visitQueue.indexOf(_value);  
                   if(index >= 0){  
                       _obj[key] = copyVisitQueue[index];  
                   }  
                   originQueue.push(_value);  
                   _obj[key] = {};  
                   copyQueue.push(_obj[key]);  
               }  
           }  
       }  
       return obj;  
   }

JSON

深拷贝对象还有另一个解决方法,在对象中不含有函数的时候,使用JSON解析反解析就可以得到一个深拷贝对象

来源:http://blog.csdn.net/sysuzhyupeng/article/details/70340598

js中对象的复制,浅复制(浅拷贝)和深复制(深拷贝)
在js中,我们经常复制一个对象,复制数据,那么就会有人问了,怎么复制,今天鹏哥就带来js中的复制方法。

JS中对象分为基本类型和复合(引用)类型,基本类型存放在栈内存,复合(引用)类型存放在堆内存。

堆内存用于存放由new创建的对象,栈内存存放一些基本类型的变量和对象的引用变量。

至于堆内存和栈内存的区别介绍,你们可以百度看看。

下面开始讲解复制:

这种只是简单的变量,内存小,我们直接复制不会发生引用。

var a=123;  
var b=a;  
a=123456;  
alert(a); //123456  
alert(b); //123  

//或者是  

var a='afafas';  
var b=a;  
a='fgfdsdsgs';  
alert(a); //fgfdsdsgs  
alert(b); //afafas

而对于对象这种内存占用比较大的来说,直接让复制的东西等于要复制的,那么就会发生引用,因为这种复制,只是将复制出来的东西的指向指向了要复制的那个东西,简单的说,就是两个都同时指向了一个空间,如果改变其中一个,另一个也会发生变化。这就发生了引用。

引用只发生在对象的身上:

var arr1=[1,2,3];  
var arr2=arr1;  
arr1.push(4);  
alert(arr1); //1234  
alert(arr2); //1234  
arr2.push(5);  
alert(arr1); //12345  
alert(arr2); //12345

那么对于数组,ES6我们复制有新的两种方法,不会发生引用。

第一种:Array.from(要复制的数组);

var arr1=[1,2,3];  
var arr2=Array.from(arr1);  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种:...

var arr1=[1,2,3];  
var arr2=[...arr1];  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种这个方法也可以用在函数的行参上面。

function show(...arr1){  //直接来复制arguments这个伪数组,让它变成真正的数组,从而拥有数组的方法。  
  alert(arr1); //1234  
  arr1.push(5);  
  alert(arr1); //12345  
}  
show(1,2,3,4)  
或者是通过循环来复制:  

var arr1=[1,2,3,4];  
var arr2=[];  
for(var i=0; i<arr1.length; i++){  
  arr2[i]=arr1[i];  
}  
arr1.push(5);  
arr2.push(6);  
alert(arr1); //12345  
alert(arr2); //12346  

//或者是json  

var json1={"name":"鹏哥","age":24,"job":"前端开发"};  
var json2={};  
for(var name in json1){  
  json2[name]=json1[name];  
}  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
json1.a=1;  
json2.b=2;  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发","a":1}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发","b":2}

深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用,

1)深复制在计算机中开辟了一块内存地址用于存放复制的对象,
  2)而浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。

所谓的浅复制,只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”。

看例子:

var json1 = {"a":"李鹏","arr1":[1,2,3]}  
function copy(obj1) {  
    var obj2 = {};  
    for (var i in obj1) {  
      obj2[i] = obj1[i];  
    }  
    return obj2;  
}  
var json2 = copy(json1);  
json1.arr1.push(4);  
alert(json1.arr1);  //1234  
alert(json2.arr1)  //1234

而深复制的话,我们要求复制一个复杂的对象,那么我们就可以利用递归的思想来做,及省性能,又不会发生引用。

看例子:

var json1={"name":"鹏哥","age":18,"arr1":[1,2,3,4,5],"string":'afasfsafa',"arr2":[1,2,3,4,5],"arr3":[{"name1":"李鹏"},{"job":"前端开发"}]};  
var json2={};  
function copy(obj1,obj2){  
  var obj2=obj2||{}; //最初的时候给它一个初始值=它自己或者是一个json  
  for(var name in obj1){  
    if(typeof obj1[name] === "object"){ //先判断一下obj[name]是不是一个对象  
      obj2[name]= (obj1[name].constructor===Array)?[]:{}; //我们让要复制的对象的name项=数组或者是json  
      copy(obj1[name],obj2[name]); //然后来无限调用函数自己 递归思想  
    }else{  
      obj2[name]=obj1[name];  //如果不是对象,直接等于即可,不会发生引用。  
    }  
  }  
  return obj2; //然后在把复制好的对象给return出去  
}  
json2=copy(json1,json2)  
json1.arr1.push(6);  
alert(json1.arr1);  //123456  
alert(json2.arr1);  //12345

作者:虚幻的锈色
链接:https://www.jianshu.com/p/0d7bd31ccf43
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JavaScript 中对象的深拷贝
在JavaScript中,对对象进行拷贝的场景比较常见。但是简单的复制语句只能对对象进行浅拷贝,即复制的是一份引用,而不是它所引用的对象。而更多的时候,我们希望对对象进行深拷贝,避免原始对象被无意修改
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
一. 浅拷贝的实现
浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可。
1.1 方法一:简单的复制语句

/* ================ 浅拷贝 ================ */  
function simpleClone(initalObj) {  
    var obj = {};  
    for ( var i in initalObj) {  
        obj[i] = initalObj[i];  
    }  
    return obj;  
}

客户端调用

/* ================ 客户端调用 ================ */  
var obj = {  
    a: "hello",  
    b: {  
        a: "world",  
        b: 21  
    },  
    c: ["Bob", "Tom", "Jenny"],  
    d: function() {  
        alert("hello world");  
    }  
}  
var cloneObj = simpleClone(obj); // 对象拷贝  

console.log(cloneObj.b); // {a: "world", b: 21}  
console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]  
console.log(cloneObj.d); // function() { alert("hello world"); }  

// 修改拷贝后的对象  
cloneObj.b.a = "changed";  
cloneObj.c = [1, 2, 3];  
cloneObj.d = function() { alert("changed"); };  

console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了  

console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改  
console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改

1.2 方法二:Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "hello", b: 21} };   
var initalObj = Object.assign({}, obj);   
initalObj.a.a = "changed";   
console.log(obj.a.a); // "changed"

二. 深拷贝的实现
要实现深拷贝有很多办法,有最简单的 JSON.parse() 方法,也有常用的递归拷贝方法,和ES5中的 Object.create() 方法。
2.1 方法一:使用 JSON.parse() 方法
在JavaScript权威指南p141序列化对象
o = {x:1,y:{z:[false,null," "]}}; //定义一个测试对象
s = JSON.stringify(o); //s是 '{"x":1,"y":{"z":[false,null," "]}}'
p = JSON.parse(s); //p是o的深拷贝
要实现深拷贝有很多办法,比如最简单的办法是使用 JSON.parse():

/* ================ 深拷贝 ================ */  
function deepClone(initalObj) {  
    var obj = {};  
    try {  
        obj = JSON.parse(JSON.stringify(initalObj));  
    }  
    return obj;  
}  

/* ================ 客户端调用 ================ */  
var obj = {  
    a: {  
        a: "world",  
        b: 21  
    }  
}  
var cloneObj = deepClone(obj);  
cloneObj.a.a = "changed";  

console.log(obj.a.a); // "world"

这种方法简单易用。
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
2.2 方法二:递归拷贝
代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        if (typeof initalObj[i] === 'object') {  
            obj[i] = (initalObj[i].constructor === Array) ? [] : {};  
            arguments.callee(initalObj[i], obj[i]);  
        } else {  
            obj[i] = initalObj[i];  
        }  
    }  
    return obj;  
}

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。
为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
改进版代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : {};  
            arguments.callee(prop, obj[i]);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

2.3 方法三:使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

三. 参考:jQuery.extend()方法的实现
jQuery.js的jQuery.extend()也实现了对象的深拷贝。下面将官方代码贴出来,以供参考。
官方链接地址:https://github.com/jquery/jquery/blob/master/src/core.js。

jQuery.extend = jQuery.fn.extend = function() {  
    var options, name, src, copy, copyIsArray, clone,  
        target = arguments[ 0 ] || {},  
        i = 1,  
        length = arguments.length,  
        deep = false;  

    // Handle a deep copy situation  
    if ( typeof target === "boolean" ) {  
        deep = target;  

        // Skip the boolean and the target  
        target = arguments[ i ] || {};  
        i++;  
    }  

    // Handle case when target is a string or something (possible in deep copy)  
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {  
        target = {};  
    }  

    // Extend jQuery itself if only one argument is passed  
    if ( i === length ) {  
        target = this;  
        i--;  
    }  

    for ( ; i < length; i++ ) {  

        // Only deal with non-null/undefined values  
        if ( ( options = arguments[ i ] ) != null ) {  

            // Extend the base object  
            for ( name in options ) {  
                src = target[ name ];  
                copy = options[ name ];  

                // Prevent never-ending loop  
                if ( target === copy ) {  
                    continue;  
                }  

                // Recurse if we're merging plain objects or arrays  
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||  
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {  

                    if ( copyIsArray ) {  
                        copyIsArray = false;  
                        clone = src && jQuery.isArray( src ) ? src : [];  

                    } else {  
                        clone = src && jQuery.isPlainObject( src ) ? src : {};  
                    }  

                    // Never move original objects, clone them  
                    target[ name ] = jQuery.extend( deep, clone, copy );  

                // Don't bring in undefined values  
                } else if ( copy !== undefined ) {  
                    target[ name ] = copy;  
                }  
            }  
        }  
    }  

    // Return the modified object  
    return target;  
};
继续阅读 »

type函数 首先我们要实现一个getType函数对元素进行类型判断,关于元素的类型判断, js中typeof和instanceof详解 ,这里用一个更简便的方法,直接调用Object.prototype.toString 方法。

  function getType(obj){  
       //tostring会返回对应不同的标签的构造函数  
       var toString = Object.prototype.toString;  
       var map = {  
          '[object Boolean]'  : 'boolean',   
          '[object Number]'   : 'number',   
          '[object String]'   : 'string',   
          '[object Function]' : 'function',   
          '[object Array]'    : 'array',   
          '[object Date]'     : 'date',   
          '[object RegExp]'   : 'regExp',   
          '[object Undefined]': 'undefined',  
          '[object Null]'     : 'null',   
          '[object Object]'   : 'object'  
      };  
      if(obj instanceof Element) {  
           return 'element';  
      }  
      return map[toString.call(obj)];  
   }  

深拷贝(deepClone)

对于一个引用类型,如果直接将它赋值给另一个变量,由于这两个引用指向同一个地址,这时改变其中任何一个引用,另一个都会受到影响。当我们想复制一个对象并且切断与这个对象的联系,就要使用深拷贝。对于一个对象来说,由于可能有多层结构,所以我们可以使用递归来解决这个问题

  function deepClone(data){  
       var type = getType(data);  
       var obj;  
       if(type === 'array'){  
           obj = [];  
       } else if(type === 'object'){  
           obj = {};  
       } else {  
           //不再具有下一层次  
           return data;  
       }  
       if(type === 'array'){  
           for(var i = 0, len = data.length; i < len; i++){  
               obj.push(deepClone(data[i]));  
           }  
       } else if(type === 'object'){  
           for(var key in data){  
               obj[key] = deepClone(data[key]);  
           }  
       }  
       return obj;  
   }

对于function类型,这里是直接赋值的,还是共享一个内存值。这是因为函数更多的是完成某些功能,有个输入值和返回值,而且对于上层业务而言更多的是完成业务功能,并不需要真正将函数深拷贝。

广度优先遍历

上面是使用递归来进行深拷贝,显然我们可以使用树的广度优先遍历来实现

//这里为了阅读方便,只深拷贝对象,关于数组的判断参照上面的例子

   function deepClone(data){  
       var obj = {};  
       var originQueue = [data];  
       var copyQueue = [obj];  
       //以下两个队列用来保存复制过程中访问过的对象,以此来避免对象环的问题(对象的某个属性值是对象本身)  
       var visitQueue = [];  
       var copyVisitQueue = [];  
       while(originQueue.length > 0){  
           var _data = originQueue.shift();  
           var _obj = copyQueue.shift();  
           visitQueue.push(_data);  
           copyVisitQueue.push(_obj);  
           for(var key in _data){  
               var _value = _data[key]  
               if(typeof _value !== 'object'){  
                   _obj[key] = _value;  
               } else {  
                   //使用indexOf可以发现数组中是否存在相同的对象(实现indexOf的难点就在于对象比较)  
                   var index = visitQueue.indexOf(_value);  
                   if(index >= 0){  
                       _obj[key] = copyVisitQueue[index];  
                   }  
                   originQueue.push(_value);  
                   _obj[key] = {};  
                   copyQueue.push(_obj[key]);  
               }  
           }  
       }  
       return obj;  
   }

JSON

深拷贝对象还有另一个解决方法,在对象中不含有函数的时候,使用JSON解析反解析就可以得到一个深拷贝对象

来源:http://blog.csdn.net/sysuzhyupeng/article/details/70340598

js中对象的复制,浅复制(浅拷贝)和深复制(深拷贝)
在js中,我们经常复制一个对象,复制数据,那么就会有人问了,怎么复制,今天鹏哥就带来js中的复制方法。

JS中对象分为基本类型和复合(引用)类型,基本类型存放在栈内存,复合(引用)类型存放在堆内存。

堆内存用于存放由new创建的对象,栈内存存放一些基本类型的变量和对象的引用变量。

至于堆内存和栈内存的区别介绍,你们可以百度看看。

下面开始讲解复制:

这种只是简单的变量,内存小,我们直接复制不会发生引用。

var a=123;  
var b=a;  
a=123456;  
alert(a); //123456  
alert(b); //123  

//或者是  

var a='afafas';  
var b=a;  
a='fgfdsdsgs';  
alert(a); //fgfdsdsgs  
alert(b); //afafas

而对于对象这种内存占用比较大的来说,直接让复制的东西等于要复制的,那么就会发生引用,因为这种复制,只是将复制出来的东西的指向指向了要复制的那个东西,简单的说,就是两个都同时指向了一个空间,如果改变其中一个,另一个也会发生变化。这就发生了引用。

引用只发生在对象的身上:

var arr1=[1,2,3];  
var arr2=arr1;  
arr1.push(4);  
alert(arr1); //1234  
alert(arr2); //1234  
arr2.push(5);  
alert(arr1); //12345  
alert(arr2); //12345

那么对于数组,ES6我们复制有新的两种方法,不会发生引用。

第一种:Array.from(要复制的数组);

var arr1=[1,2,3];  
var arr2=Array.from(arr1);  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种:...

var arr1=[1,2,3];  
var arr2=[...arr1];  
arr1.push(4);  
alert(arr1);  //1234  
alert(arr2);  //123  
arr2.push(5);  
alert(arr1);  //1234  
alert(arr2);  //1235

第二种这个方法也可以用在函数的行参上面。

function show(...arr1){  //直接来复制arguments这个伪数组,让它变成真正的数组,从而拥有数组的方法。  
  alert(arr1); //1234  
  arr1.push(5);  
  alert(arr1); //12345  
}  
show(1,2,3,4)  
或者是通过循环来复制:  

var arr1=[1,2,3,4];  
var arr2=[];  
for(var i=0; i<arr1.length; i++){  
  arr2[i]=arr1[i];  
}  
arr1.push(5);  
arr2.push(6);  
alert(arr1); //12345  
alert(arr2); //12346  

//或者是json  

var json1={"name":"鹏哥","age":24,"job":"前端开发"};  
var json2={};  
for(var name in json1){  
  json2[name]=json1[name];  
}  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发"}  
json1.a=1;  
json2.b=2;  
alert(JSON.stringify(json1)); //{"name":"鹏哥","age":24,"job":"前端开发","a":1}  
alert(JSON.stringify(json2)); //{"name":"鹏哥","age":24,"job":"前端开发","b":2}

深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用,

1)深复制在计算机中开辟了一块内存地址用于存放复制的对象,
  2)而浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。

所谓的浅复制,只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”。

看例子:

var json1 = {"a":"李鹏","arr1":[1,2,3]}  
function copy(obj1) {  
    var obj2 = {};  
    for (var i in obj1) {  
      obj2[i] = obj1[i];  
    }  
    return obj2;  
}  
var json2 = copy(json1);  
json1.arr1.push(4);  
alert(json1.arr1);  //1234  
alert(json2.arr1)  //1234

而深复制的话,我们要求复制一个复杂的对象,那么我们就可以利用递归的思想来做,及省性能,又不会发生引用。

看例子:

var json1={"name":"鹏哥","age":18,"arr1":[1,2,3,4,5],"string":'afasfsafa',"arr2":[1,2,3,4,5],"arr3":[{"name1":"李鹏"},{"job":"前端开发"}]};  
var json2={};  
function copy(obj1,obj2){  
  var obj2=obj2||{}; //最初的时候给它一个初始值=它自己或者是一个json  
  for(var name in obj1){  
    if(typeof obj1[name] === "object"){ //先判断一下obj[name]是不是一个对象  
      obj2[name]= (obj1[name].constructor===Array)?[]:{}; //我们让要复制的对象的name项=数组或者是json  
      copy(obj1[name],obj2[name]); //然后来无限调用函数自己 递归思想  
    }else{  
      obj2[name]=obj1[name];  //如果不是对象,直接等于即可,不会发生引用。  
    }  
  }  
  return obj2; //然后在把复制好的对象给return出去  
}  
json2=copy(json1,json2)  
json1.arr1.push(6);  
alert(json1.arr1);  //123456  
alert(json2.arr1);  //12345

作者:虚幻的锈色
链接:https://www.jianshu.com/p/0d7bd31ccf43
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JavaScript 中对象的深拷贝
在JavaScript中,对对象进行拷贝的场景比较常见。但是简单的复制语句只能对对象进行浅拷贝,即复制的是一份引用,而不是它所引用的对象。而更多的时候,我们希望对对象进行深拷贝,避免原始对象被无意修改
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
对象的深拷贝与浅拷贝的区别如下:
浅拷贝:仅仅复制对象的引用,而不是对象本身;
深拷贝:把复制的对象所引用的全部对象都复制一遍。
一. 浅拷贝的实现
浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可。
1.1 方法一:简单的复制语句

/* ================ 浅拷贝 ================ */  
function simpleClone(initalObj) {  
    var obj = {};  
    for ( var i in initalObj) {  
        obj[i] = initalObj[i];  
    }  
    return obj;  
}

客户端调用

/* ================ 客户端调用 ================ */  
var obj = {  
    a: "hello",  
    b: {  
        a: "world",  
        b: 21  
    },  
    c: ["Bob", "Tom", "Jenny"],  
    d: function() {  
        alert("hello world");  
    }  
}  
var cloneObj = simpleClone(obj); // 对象拷贝  

console.log(cloneObj.b); // {a: "world", b: 21}  
console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]  
console.log(cloneObj.d); // function() { alert("hello world"); }  

// 修改拷贝后的对象  
cloneObj.b.a = "changed";  
cloneObj.c = [1, 2, 3];  
cloneObj.d = function() { alert("changed"); };  

console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了  

console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改  
console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改

1.2 方法二:Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "hello", b: 21} };   
var initalObj = Object.assign({}, obj);   
initalObj.a.a = "changed";   
console.log(obj.a.a); // "changed"

二. 深拷贝的实现
要实现深拷贝有很多办法,有最简单的 JSON.parse() 方法,也有常用的递归拷贝方法,和ES5中的 Object.create() 方法。
2.1 方法一:使用 JSON.parse() 方法
在JavaScript权威指南p141序列化对象
o = {x:1,y:{z:[false,null," "]}}; //定义一个测试对象
s = JSON.stringify(o); //s是 '{"x":1,"y":{"z":[false,null," "]}}'
p = JSON.parse(s); //p是o的深拷贝
要实现深拷贝有很多办法,比如最简单的办法是使用 JSON.parse():

/* ================ 深拷贝 ================ */  
function deepClone(initalObj) {  
    var obj = {};  
    try {  
        obj = JSON.parse(JSON.stringify(initalObj));  
    }  
    return obj;  
}  

/* ================ 客户端调用 ================ */  
var obj = {  
    a: {  
        a: "world",  
        b: 21  
    }  
}  
var cloneObj = deepClone(obj);  
cloneObj.a.a = "changed";  

console.log(obj.a.a); // "world"

这种方法简单易用。
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
2.2 方法二:递归拷贝
代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        if (typeof initalObj[i] === 'object') {  
            obj[i] = (initalObj[i].constructor === Array) ? [] : {};  
            arguments.callee(initalObj[i], obj[i]);  
        } else {  
            obj[i] = initalObj[i];  
        }  
    }  
    return obj;  
}

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。
为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。
改进版代码如下:

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : {};  
            arguments.callee(prop, obj[i]);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

2.3 方法三:使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

/* ================ 深拷贝 ================ */  
function deepClone(initalObj, finalObj) {  
    var obj = finalObj || {};  
    for (var i in initalObj) {  
        var prop = initalObj[i];  

        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况  
        if(prop === obj) {  
            continue;  
        }  

        if (typeof prop === 'object') {  
            obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);  
        } else {  
            obj[i] = prop;  
        }  
    }  
    return obj;  
}

三. 参考:jQuery.extend()方法的实现
jQuery.js的jQuery.extend()也实现了对象的深拷贝。下面将官方代码贴出来,以供参考。
官方链接地址:https://github.com/jquery/jquery/blob/master/src/core.js。

jQuery.extend = jQuery.fn.extend = function() {  
    var options, name, src, copy, copyIsArray, clone,  
        target = arguments[ 0 ] || {},  
        i = 1,  
        length = arguments.length,  
        deep = false;  

    // Handle a deep copy situation  
    if ( typeof target === "boolean" ) {  
        deep = target;  

        // Skip the boolean and the target  
        target = arguments[ i ] || {};  
        i++;  
    }  

    // Handle case when target is a string or something (possible in deep copy)  
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {  
        target = {};  
    }  

    // Extend jQuery itself if only one argument is passed  
    if ( i === length ) {  
        target = this;  
        i--;  
    }  

    for ( ; i < length; i++ ) {  

        // Only deal with non-null/undefined values  
        if ( ( options = arguments[ i ] ) != null ) {  

            // Extend the base object  
            for ( name in options ) {  
                src = target[ name ];  
                copy = options[ name ];  

                // Prevent never-ending loop  
                if ( target === copy ) {  
                    continue;  
                }  

                // Recurse if we're merging plain objects or arrays  
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||  
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {  

                    if ( copyIsArray ) {  
                        copyIsArray = false;  
                        clone = src && jQuery.isArray( src ) ? src : [];  

                    } else {  
                        clone = src && jQuery.isPlainObject( src ) ? src : {};  
                    }  

                    // Never move original objects, clone them  
                    target[ name ] = jQuery.extend( deep, clone, copy );  

                // Don't bring in undefined values  
                } else if ( copy !== undefined ) {  
                    target[ name ] = copy;  
                }  
            }  
        }  
    }  

    // Return the modified object  
    return target;  
};
收起阅读 »

推送消息

不好意思弄错了,是小弟学艺不精

不好意思弄错了,是小弟学艺不精

HBuilder 技术点

HBuilder

1.如果是在当前其他任意Activity页面跳回到5+ 的页面,可以这样:

Button btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent=new Intent();
intent.setClass(Second.this, io.dcloud.PandoraEntry.class);
startActivity(intent);
}
});
2.

继续阅读 »

1.如果是在当前其他任意Activity页面跳回到5+ 的页面,可以这样:

Button btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent=new Intent();
intent.setClass(Second.this, io.dcloud.PandoraEntry.class);
startActivity(intent);
}
});
2.

收起阅读 »

前端常见跨域解决方案

AJAX跨域 跨域 跨域请求

什么是跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
javascript
广义的跨域:


1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等**


其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
跨域问题的产生背景

早期为了防止CSRF(跨域请求伪造)的攻击,浏览器引入了同源策略(SOP)来提高安全性。

CSRF(Cross-site request forgery),跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。 —— 浅谈CSRF 攻击方式
而所谓"同源策略",即同域名(domain或ip)、同端口、同协议的才能互相获取资源,而不能访问其他域的资源。在同源策略影响下,一个域名A的网页可以获取域名B下的脚本,css,图片等,但是不能发送Ajax请求,也不能操作Cookie、LocalStorage等数据。同源策略的存在,一方面提高了网站的安全性,但同时在面对前后端分离、模拟测试等场景时,也带来了一些麻烦,从而不得不寻求一些方法来突破限制,获取资源。
什么是同源策略?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:

1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
常见跨域场景

URL                                      说明                    是否允许通信  
http://www.domain.com/a.js  
http://www.domain.com/b.js         同一域名,不同文件或路径           允许  
http://www.domain.com/lab/c.js  

http://www.domain.com:8000/a.js  
http://www.domain.com/b.js         同一域名,不同端口                不允许  

http://www.domain.com/a.js  
https://www.domain.com/b.js        同一域名,不同协议                不允许  

http://www.domain.com/a.js  
http://192.168.4.12/b.js           域名和域名对应相同ip              不允许  

http://www.domain.com/a.js  
http://x.domain.com/b.js           主域相同,子域不同                不允许  
http://domain.com/c.js  

http://www.domain1.com/a.js  
http://www.domain2.com/b.js        不同域名                         不允许  

跨域解决方案
1.通过jsonp跨域

  1. document.domain + iframe 跨域
  2. location.hash + iframe
  3. window.name + iframe跨域
  4. 跨域资源共享(CORS)
  5. nginx代理跨域
  6. WebSocket协议跨域
    一、 通过jsonp跨域
    通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
    1.)原生实现:

    <script>  
    var script = document.createElement('script');  
    script.type = 'text/javascript';  
    
    // 传参并指定回调执行函数为onBack  
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';  
    document.head.appendChild(script);  
    
    // 回调执行函数  
    function onBack(res) {  
        alert(JSON.stringify(res));  
    }  
    </script>

    服务端返回如下(返回时即执行全局函数):

    onBack({"status": true, "user": "admin"})

2.)jquery ajax:

$.ajax({  
    url: 'http://www.domain2.com:8080/login',  
    type: 'get',  
    dataType: 'jsonp',  // 请求方式为jsonp  
    jsonpCallback: "onBack",    // 自定义回调函数名  
    data: {}  
});

3.)vue.js:

this.$http.jsonp('http://www.domain2.com:8080/login', {  
    params: {},  
    jsonp: 'onBack'  
}).then((res) => {  
    console.log(res);   
})

后端node.js代码示例:

var querystring = require('querystring');  
var http = require('http');  
var server = http.createServer();  

server.on('request', function(req, res) {  
    var params = qs.parse(req.url.split('?')[1]);  
    var fn = params.callback;  

    // jsonp返回设置  
    res.writeHead(200, { 'Content-Type': 'text/javascript' });  
    res.write(fn + '(' + JSON.stringify(params) + ')');  

    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

jsonp缺点:只能实现get一种请求。

二、 document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1.)父窗口:(http://www.domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>  
<script>  
    document.domain = 'domain.com';  
    var user = 'admin';  
</script>

2.)子窗口:(http://child.domain.com/b.html)

<script>  
    document.domain = 'domain.com';  
    // 获取父窗口中变量  
    alert('get js data from parent ---> ' + window.parent.user);  
</script>

三、 location.hash + iframe跨域
实现原理:a想要与b跨域相互通信,通过中间页c来实现。三个页面,不同于之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>  
<script>  
    var iframe = document.getElementById('iframe');  

    // 向b.html传hash值  
    setTimeout(function() {  
        iframe.src = iframe.src + '#user=admin';  
    }, 1000);  

    // 开放给同域c.html的回调方法  
    function onCallback(res) {  
        alert('data from c.html ---> ' + res);  
    }  
</script>

2.)b.html:(http://www.domain2.com/b.html)

<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>  
<script>  
    var iframe = document.getElementById('iframe');  

    // 监听a.html传来的hash值,再传给c.html  
    window.onhashchange = function () {  
        iframe.src = iframe.src + location.hash;  
    };  
</script>

3.)c.html:(http://www.domain1.com/c.html)

<script>  
    // 监听b.html传来的hash值  
    window.onhashchange = function () {  
        // 再通过操作同域a.html的js回调,将结果传回  
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));  
    };  
</script>

四、 window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

1.)a.html:(http://www.domain1.com/a.html)

var proxy = function(url, callback) {  
    var state = 0;  
    var iframe = document.createElement('iframe');  

    // 加载跨域页面  
    iframe.src = url;  

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name  
    iframe.onload = function() {  
        if (state === 1) {  
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据  
            callback(iframe.contentWindow.name);  
            destoryFrame();  

        } else if (state === 0) {  
            // 第1次onload(跨域页)成功后,切换到同域代理页面  
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';  
            state = 1;  
        }  
    };  

    document.body.appendChild(iframe);  

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)  
    function destoryFrame() {  
        iframe.contentWindow.document.write('');  
        iframe.contentWindow.close();  
        document.body.removeChild(iframe);  
    }  
};

// 请求跨域b页面数据

proxy('http://www.domain2.com/b.html', function(data){  
    alert(data);  
});

2.)proxy.html:(http://www.domain1.com/proxy....
中间代理页,与a.html同域,内容为空即可。

3.)b.html:(http://www.domain2.com/b.html)

<script>  
    window.name = 'This is domain2 data!';  
</script>

总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
五、 postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>  
<script>         
    var iframe = document.getElementById('iframe');  
    iframe.onload = function() {  
        var data = {  
            name: 'aym'  
        };  
        // 向domain2传送跨域数据  
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');  
    };  

    // 接受domain2返回数据  
    window.addEventListener('message', function(e) {  
        alert('data from domain2 ---> ' + e.data);  
    }, false);  
</script>

2.)b.html:(http://www.domain2.com/b.html)

<script>  
    // 接收domain1的数据  
    window.addEventListener('message', function(e) {  
        alert('data from domain1 ---> ' + e.data);  

        var data = JSON.parse(e.data);  
        if (data) {  
            data.number = 16;  

            // 处理后再发回domain1  
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');  
        }  
    }, false);  
</script>

六、 跨域资源共享(CORS)

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:七、nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。

目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。

1、 前端设置:

1.)原生ajax

// 前端设置是否带cookie

xhr.withCredentials = true;

示例代码:

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容  

// 前端设置是否带cookie  

xhr.withCredentials = true;  

xhr.open('post', 'http://www.domain2.com:8080/login', true);  
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');  
xhr.send('user=admin');  

xhr.onreadystatechange = function() {  
    if (xhr.readyState == 4 && xhr.status == 200) {  
        alert(xhr.responseText);  
    }  
};

2.)jQuery ajax

$.ajax({  
    ...  
   xhrFields: {  
       withCredentials: true    // 前端设置是否带cookie  
   },  
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie  
    ...  
});

3.)vue框架
在vue-resource封装的ajax组件中加入以下代码:

Vue.http.options.credentials = true
2、 服务端设置:

若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。

1.)Java后台:  

/*  
 * 导入包:import javax.servlet.http.HttpServletResponse;  
 * 接口参数中定义:HttpServletResponse response  
 */  
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");  // 若有端口需写全(协议+域名+端口)  
response.setHeader("Access-Control-Allow-Credentials", "true");

2.)Nodejs后台示例:

var http = require('http');  
var server = http.createServer();  
var qs = require('querystring');  

server.on('request', function(req, res) {  
    var postData = '';  

    // 数据块接收中  
    req.addListener('data', function(chunk) {  
        postData += chunk;  
    });  

    // 数据接收完毕  
    req.addListener('end', function() {  
        postData = qs.parse(postData);  

        // 跨域后台设置  
        res.writeHead(200, {  
            'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie  
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)  
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取cookie  
        });  

        res.write(JSON.stringify(postData));  
        res.end();  
    });  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

七、 nginx代理跨域

1、 nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {  
  add_header Access-Control-Allow-Origin *;  
}

2、 nginx反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:

#proxy服务器  
server {  
    listen       81;  
    server_name  www.domain1.com;  

    location / {  
        proxy_pass   http://www.domain2.com:8080;  #反向代理  
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名  
        index  index.html index.htm;  

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用  
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*  
        add_header Access-Control-Allow-Credentials true;  
    }  
}

1.) 前端代码示例:

var xhr = new XMLHttpRequest();  

// 前端开关:浏览器是否读写cookie  
xhr.withCredentials = true;  

// 访问nginx中的代理服务器  
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);  
xhr.send();

2.) Nodejs后台示例:

var http = require('http');  
var server = http.createServer();  
var qs = require('querystring');  

server.on('request', function(req, res) {  
    var params = qs.parse(req.url.substring(2));  

    // 向前台写cookie  
    res.writeHead(200, {  
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取  
    });  

    res.write(JSON.stringify(params));  
    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

八、 Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1、 非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一个proxy服务器。

1.)前端代码示例:

var xhr = new XMLHttpRequest();  

// 前端开关:浏览器是否读写cookie  
xhr.withCredentials = true;  

// 访问http-proxy-middleware代理服务器  
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);  
xhr.send();

2.)中间件服务器:

var express = require('express');  
var proxy = require('http-proxy-middleware');  
var app = express();  

app.use('/', proxy({  
    // 代理跨域目标接口  
    target: 'http://www.domain2.com:8080',  
    changeOrigin: true,  

    // 修改响应头信息,实现跨域并允许带cookie  
    onProxyRes: function(proxyRes, req, res) {  
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');  
        res.header('Access-Control-Allow-Credentials', 'true');  
    },  

    // 修改响应信息中的cookie域名  
    cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改  
}));  

app.listen(3000);  
console.log('Proxy server is listen at port 3000...');

3.)Nodejs后台同(六:nginx)

2、 vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

webpack.config.js部分配置:

module.exports = {  
    entry: {},  
    module: {},  
    ...  
    devServer: {  
        historyApiFallback: true,  
        proxy: [{  
            context: '/login',  
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口  
            changeOrigin: true,  
            secure: false,  // 当代理某些https服务报错时用  
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改  
        }],  
        noInfo: true  
    }  
}

九、 WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1.)前端代码:

<div>user input:<input type="text"></div>  
<script src="./socket.io.js"></script>  
<script>  
var socket = io('http://www.domain2.com:8080');  

// 连接成功处理  
socket.on('connect', function() {  
    // 监听服务端消息  
    socket.on('message', function(msg) {  
        console.log('data from server: ---> ' + msg);   
    });  

    // 监听服务端关闭  
    socket.on('disconnect', function() {   
        console.log('Server socket has closed.');   
    });  
});  

document.getElementsByTagName('input')[0].onblur = function() {  
    socket.send(this.value);  
};  
</script>

2.)Nodejs socket后台:

var http = require('http');  
var socket = require('socket.io');  

// 启http服务  
var server = http.createServer(function(req, res) {  
    res.writeHead(200, {  
        'Content-type': 'text/html'  
    });  
    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');  

// 监听socket连接  
socket.listen(server).on('connection', function(client) {  
    // 接收信息  
    client.on('message', function(msg) {  
        client.send('hello:' + msg);  
        console.log('data from client: ---> ' + msg);  
    });  

    // 断开处理  
    client.on('disconnect', function() {  
        console.log('Client socket has closed.');   
    });  
});  

来源:https://segmentfault.com/a/1190000011145364

cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是两个网页,一级域名相同,二级域名不同,浏览器允许通过设置document.domain="example.com"来共享cookie。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

继续阅读 »

什么是跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
javascript
广义的跨域:


1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等**


其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
跨域问题的产生背景

早期为了防止CSRF(跨域请求伪造)的攻击,浏览器引入了同源策略(SOP)来提高安全性。

CSRF(Cross-site request forgery),跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。 —— 浅谈CSRF 攻击方式
而所谓"同源策略",即同域名(domain或ip)、同端口、同协议的才能互相获取资源,而不能访问其他域的资源。在同源策略影响下,一个域名A的网页可以获取域名B下的脚本,css,图片等,但是不能发送Ajax请求,也不能操作Cookie、LocalStorage等数据。同源策略的存在,一方面提高了网站的安全性,但同时在面对前后端分离、模拟测试等场景时,也带来了一些麻烦,从而不得不寻求一些方法来突破限制,获取资源。
什么是同源策略?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:

1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
常见跨域场景

URL                                      说明                    是否允许通信  
http://www.domain.com/a.js  
http://www.domain.com/b.js         同一域名,不同文件或路径           允许  
http://www.domain.com/lab/c.js  

http://www.domain.com:8000/a.js  
http://www.domain.com/b.js         同一域名,不同端口                不允许  

http://www.domain.com/a.js  
https://www.domain.com/b.js        同一域名,不同协议                不允许  

http://www.domain.com/a.js  
http://192.168.4.12/b.js           域名和域名对应相同ip              不允许  

http://www.domain.com/a.js  
http://x.domain.com/b.js           主域相同,子域不同                不允许  
http://domain.com/c.js  

http://www.domain1.com/a.js  
http://www.domain2.com/b.js        不同域名                         不允许  

跨域解决方案
1.通过jsonp跨域

  1. document.domain + iframe 跨域
  2. location.hash + iframe
  3. window.name + iframe跨域
  4. 跨域资源共享(CORS)
  5. nginx代理跨域
  6. WebSocket协议跨域
    一、 通过jsonp跨域
    通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
    1.)原生实现:

    <script>  
    var script = document.createElement('script');  
    script.type = 'text/javascript';  
    
    // 传参并指定回调执行函数为onBack  
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';  
    document.head.appendChild(script);  
    
    // 回调执行函数  
    function onBack(res) {  
        alert(JSON.stringify(res));  
    }  
    </script>

    服务端返回如下(返回时即执行全局函数):

    onBack({"status": true, "user": "admin"})

2.)jquery ajax:

$.ajax({  
    url: 'http://www.domain2.com:8080/login',  
    type: 'get',  
    dataType: 'jsonp',  // 请求方式为jsonp  
    jsonpCallback: "onBack",    // 自定义回调函数名  
    data: {}  
});

3.)vue.js:

this.$http.jsonp('http://www.domain2.com:8080/login', {  
    params: {},  
    jsonp: 'onBack'  
}).then((res) => {  
    console.log(res);   
})

后端node.js代码示例:

var querystring = require('querystring');  
var http = require('http');  
var server = http.createServer();  

server.on('request', function(req, res) {  
    var params = qs.parse(req.url.split('?')[1]);  
    var fn = params.callback;  

    // jsonp返回设置  
    res.writeHead(200, { 'Content-Type': 'text/javascript' });  
    res.write(fn + '(' + JSON.stringify(params) + ')');  

    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

jsonp缺点:只能实现get一种请求。

二、 document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1.)父窗口:(http://www.domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>  
<script>  
    document.domain = 'domain.com';  
    var user = 'admin';  
</script>

2.)子窗口:(http://child.domain.com/b.html)

<script>  
    document.domain = 'domain.com';  
    // 获取父窗口中变量  
    alert('get js data from parent ---> ' + window.parent.user);  
</script>

三、 location.hash + iframe跨域
实现原理:a想要与b跨域相互通信,通过中间页c来实现。三个页面,不同于之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>  
<script>  
    var iframe = document.getElementById('iframe');  

    // 向b.html传hash值  
    setTimeout(function() {  
        iframe.src = iframe.src + '#user=admin';  
    }, 1000);  

    // 开放给同域c.html的回调方法  
    function onCallback(res) {  
        alert('data from c.html ---> ' + res);  
    }  
</script>

2.)b.html:(http://www.domain2.com/b.html)

<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>  
<script>  
    var iframe = document.getElementById('iframe');  

    // 监听a.html传来的hash值,再传给c.html  
    window.onhashchange = function () {  
        iframe.src = iframe.src + location.hash;  
    };  
</script>

3.)c.html:(http://www.domain1.com/c.html)

<script>  
    // 监听b.html传来的hash值  
    window.onhashchange = function () {  
        // 再通过操作同域a.html的js回调,将结果传回  
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));  
    };  
</script>

四、 window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

1.)a.html:(http://www.domain1.com/a.html)

var proxy = function(url, callback) {  
    var state = 0;  
    var iframe = document.createElement('iframe');  

    // 加载跨域页面  
    iframe.src = url;  

    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name  
    iframe.onload = function() {  
        if (state === 1) {  
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据  
            callback(iframe.contentWindow.name);  
            destoryFrame();  

        } else if (state === 0) {  
            // 第1次onload(跨域页)成功后,切换到同域代理页面  
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';  
            state = 1;  
        }  
    };  

    document.body.appendChild(iframe);  

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)  
    function destoryFrame() {  
        iframe.contentWindow.document.write('');  
        iframe.contentWindow.close();  
        document.body.removeChild(iframe);  
    }  
};

// 请求跨域b页面数据

proxy('http://www.domain2.com/b.html', function(data){  
    alert(data);  
});

2.)proxy.html:(http://www.domain1.com/proxy....
中间代理页,与a.html同域,内容为空即可。

3.)b.html:(http://www.domain2.com/b.html)

<script>  
    window.name = 'This is domain2 data!';  
</script>

总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
五、 postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>  
<script>         
    var iframe = document.getElementById('iframe');  
    iframe.onload = function() {  
        var data = {  
            name: 'aym'  
        };  
        // 向domain2传送跨域数据  
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');  
    };  

    // 接受domain2返回数据  
    window.addEventListener('message', function(e) {  
        alert('data from domain2 ---> ' + e.data);  
    }, false);  
</script>

2.)b.html:(http://www.domain2.com/b.html)

<script>  
    // 接收domain1的数据  
    window.addEventListener('message', function(e) {  
        alert('data from domain1 ---> ' + e.data);  

        var data = JSON.parse(e.data);  
        if (data) {  
            data.number = 16;  

            // 处理后再发回domain1  
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');  
        }  
    }, false);  
</script>

六、 跨域资源共享(CORS)

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。

需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:七、nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。

目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。

1、 前端设置:

1.)原生ajax

// 前端设置是否带cookie

xhr.withCredentials = true;

示例代码:

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容  

// 前端设置是否带cookie  

xhr.withCredentials = true;  

xhr.open('post', 'http://www.domain2.com:8080/login', true);  
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');  
xhr.send('user=admin');  

xhr.onreadystatechange = function() {  
    if (xhr.readyState == 4 && xhr.status == 200) {  
        alert(xhr.responseText);  
    }  
};

2.)jQuery ajax

$.ajax({  
    ...  
   xhrFields: {  
       withCredentials: true    // 前端设置是否带cookie  
   },  
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie  
    ...  
});

3.)vue框架
在vue-resource封装的ajax组件中加入以下代码:

Vue.http.options.credentials = true
2、 服务端设置:

若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。

1.)Java后台:  

/*  
 * 导入包:import javax.servlet.http.HttpServletResponse;  
 * 接口参数中定义:HttpServletResponse response  
 */  
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");  // 若有端口需写全(协议+域名+端口)  
response.setHeader("Access-Control-Allow-Credentials", "true");

2.)Nodejs后台示例:

var http = require('http');  
var server = http.createServer();  
var qs = require('querystring');  

server.on('request', function(req, res) {  
    var postData = '';  

    // 数据块接收中  
    req.addListener('data', function(chunk) {  
        postData += chunk;  
    });  

    // 数据接收完毕  
    req.addListener('end', function() {  
        postData = qs.parse(postData);  

        // 跨域后台设置  
        res.writeHead(200, {  
            'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie  
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)  
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取cookie  
        });  

        res.write(JSON.stringify(postData));  
        res.end();  
    });  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

七、 nginx代理跨域

1、 nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {  
  add_header Access-Control-Allow-Origin *;  
}

2、 nginx反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:

#proxy服务器  
server {  
    listen       81;  
    server_name  www.domain1.com;  

    location / {  
        proxy_pass   http://www.domain2.com:8080;  #反向代理  
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名  
        index  index.html index.htm;  

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用  
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*  
        add_header Access-Control-Allow-Credentials true;  
    }  
}

1.) 前端代码示例:

var xhr = new XMLHttpRequest();  

// 前端开关:浏览器是否读写cookie  
xhr.withCredentials = true;  

// 访问nginx中的代理服务器  
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);  
xhr.send();

2.) Nodejs后台示例:

var http = require('http');  
var server = http.createServer();  
var qs = require('querystring');  

server.on('request', function(req, res) {  
    var params = qs.parse(req.url.substring(2));  

    // 向前台写cookie  
    res.writeHead(200, {  
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取  
    });  

    res.write(JSON.stringify(params));  
    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');

八、 Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1、 非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一个proxy服务器。

1.)前端代码示例:

var xhr = new XMLHttpRequest();  

// 前端开关:浏览器是否读写cookie  
xhr.withCredentials = true;  

// 访问http-proxy-middleware代理服务器  
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);  
xhr.send();

2.)中间件服务器:

var express = require('express');  
var proxy = require('http-proxy-middleware');  
var app = express();  

app.use('/', proxy({  
    // 代理跨域目标接口  
    target: 'http://www.domain2.com:8080',  
    changeOrigin: true,  

    // 修改响应头信息,实现跨域并允许带cookie  
    onProxyRes: function(proxyRes, req, res) {  
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');  
        res.header('Access-Control-Allow-Credentials', 'true');  
    },  

    // 修改响应信息中的cookie域名  
    cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改  
}));  

app.listen(3000);  
console.log('Proxy server is listen at port 3000...');

3.)Nodejs后台同(六:nginx)

2、 vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

webpack.config.js部分配置:

module.exports = {  
    entry: {},  
    module: {},  
    ...  
    devServer: {  
        historyApiFallback: true,  
        proxy: [{  
            context: '/login',  
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口  
            changeOrigin: true,  
            secure: false,  // 当代理某些https服务报错时用  
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改  
        }],  
        noInfo: true  
    }  
}

九、 WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1.)前端代码:

<div>user input:<input type="text"></div>  
<script src="./socket.io.js"></script>  
<script>  
var socket = io('http://www.domain2.com:8080');  

// 连接成功处理  
socket.on('connect', function() {  
    // 监听服务端消息  
    socket.on('message', function(msg) {  
        console.log('data from server: ---> ' + msg);   
    });  

    // 监听服务端关闭  
    socket.on('disconnect', function() {   
        console.log('Server socket has closed.');   
    });  
});  

document.getElementsByTagName('input')[0].onblur = function() {  
    socket.send(this.value);  
};  
</script>

2.)Nodejs socket后台:

var http = require('http');  
var socket = require('socket.io');  

// 启http服务  
var server = http.createServer(function(req, res) {  
    res.writeHead(200, {  
        'Content-type': 'text/html'  
    });  
    res.end();  
});  

server.listen('8080');  
console.log('Server is running at port 8080...');  

// 监听socket连接  
socket.listen(server).on('connection', function(client) {  
    // 接收信息  
    client.on('message', function(msg) {  
        client.send('hello:' + msg);  
        console.log('data from client: ---> ' + msg);  
    });  

    // 断开处理  
    client.on('disconnect', function() {  
        console.log('Client socket has closed.');   
    });  
});  

来源:https://segmentfault.com/a/1190000011145364

cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是两个网页,一级域名相同,二级域名不同,浏览器允许通过设置document.domain="example.com"来共享cookie。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

收起阅读 »

beecloud集成支付,微信支付提示-100,无法支付(用的还是官方的发布的hello mui App)

beecloud

从官方扫码下载的hello mui APP,安装到手机上后,支付宝能成功,微信在第一次安装,第一次进行支付,点取消后在进行支付时就报告-100错误

从官方扫码下载的hello mui APP,安装到手机上后,支付宝能成功,微信在第一次安装,第一次进行支付,点取消后在进行支付时就报告-100错误

得到url,转化成base64.

下载blobUtil js 文件

//创建下载任务
blobUtil.imgSrcToDataURL(loadUrl+big,'image/'+imgPostfix, 'Anonymous', 1.0).then(function (dataURL) {里面是加密的操作代码,dataURL是base64数据,而lloadUrl+big 就是图片网址,第二个参数就是图片后缀如jpg.
// success
var encryptPath = {};
var string100 = dataURL.substring(0, 100);
var string101ToEnd = dataURL.substring(100);

              encryptPath.filename = '_downloads/'+name+'.txt';  
              encryptPath.encrypted = aesEncrypt(JSON.stringify(string100), initKey);  
           //这里记录的是下载下来到本地的路径;  
            recordDownLoadData(userDownLoadData,downData,index,'',encryptPath,length);  
            //记录整张照片的base64-100字符后面数据,使用创建文件的方式  
            console.log(encryptPath.filename);  
            writeFile(encryptPath.filename,string101ToEnd);  
            console.log('下载第'+index+'张结束');  

            //计算下载图片;  
            index++;  

            //继续下载;  
            caseImageDown(caseImageTasks,downData,userDownLoadData,length,index);  
        }).catch(function (err) {  
          // error  
          console.log(err);  
        });
继续阅读 »

下载blobUtil js 文件

//创建下载任务
blobUtil.imgSrcToDataURL(loadUrl+big,'image/'+imgPostfix, 'Anonymous', 1.0).then(function (dataURL) {里面是加密的操作代码,dataURL是base64数据,而lloadUrl+big 就是图片网址,第二个参数就是图片后缀如jpg.
// success
var encryptPath = {};
var string100 = dataURL.substring(0, 100);
var string101ToEnd = dataURL.substring(100);

              encryptPath.filename = '_downloads/'+name+'.txt';  
              encryptPath.encrypted = aesEncrypt(JSON.stringify(string100), initKey);  
           //这里记录的是下载下来到本地的路径;  
            recordDownLoadData(userDownLoadData,downData,index,'',encryptPath,length);  
            //记录整张照片的base64-100字符后面数据,使用创建文件的方式  
            console.log(encryptPath.filename);  
            writeFile(encryptPath.filename,string101ToEnd);  
            console.log('下载第'+index+'张结束');  

            //计算下载图片;  
            index++;  

            //继续下载;  
            caseImageDown(caseImageTasks,downData,userDownLoadData,length,index);  
        }).catch(function (err) {  
          // error  
          console.log(err);  
        });
收起阅读 »