Android - 获取机子总内存,剩余内存
群友问的,帮忙翻译的 0.0
安卓获取机子总内存:
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = localBufferedReader.readLine().toString()
console.log(str)
安卓获取机子剩余:
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = ''
var i = 0
while(i<3){
str = localBufferedReader.readLine().toString();
i++
}
console.log(str)
两者输出的str需要做一下文本格式化,单位为kb,自己除以1024即可。
====== 2018-01-09 修复一个可能存在的问题,重新封装下
function getMemInfo(){
plus.android.importClass('java.io.BufferedReader')
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = localBufferedReader.readLine().toString();
var totalMemStr = str;
var avaMemStr = ''
var i = 0
while(i<2){
str = localBufferedReader.readLine().toString();
avaMemStr = str;
i++
}
totalMemStr = (parseInt(totalMemStr.toUpperCase().replace(/(( )|(:)|[A-Z])/gi,''))/1024).toFixed(0)
avaMemStr = (parseInt(avaMemStr.toUpperCase().replace(/(( )|(:)|[A-Z])/gi,''))/1024).toFixed(0)
console.log(totalMemStr);
console.log(avaMemStr);
return {total:totalMemStr,ava:avaMemStr}
}
getMemInfo();
getMemInfo返回一个对象,total是总内存,ava是剩余内存,单位为MB。
顺带自己的Github项目,一个前端跨平台方法兼容库
Sh.js github地址
群友问的,帮忙翻译的 0.0
安卓获取机子总内存:
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = localBufferedReader.readLine().toString()
console.log(str)
安卓获取机子剩余:
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = ''
var i = 0
while(i<3){
str = localBufferedReader.readLine().toString();
i++
}
console.log(str)
两者输出的str需要做一下文本格式化,单位为kb,自己除以1024即可。
====== 2018-01-09 修复一个可能存在的问题,重新封装下
function getMemInfo(){
plus.android.importClass('java.io.BufferedReader')
var localFileReader = plus.android.newObject('java.io.FileReader','/proc/meminfo')
var localBufferedReader = plus.android.newObject('java.io.BufferedReader',localFileReader,8192)
var str = localBufferedReader.readLine().toString();
var totalMemStr = str;
var avaMemStr = ''
var i = 0
while(i<2){
str = localBufferedReader.readLine().toString();
avaMemStr = str;
i++
}
totalMemStr = (parseInt(totalMemStr.toUpperCase().replace(/(( )|(:)|[A-Z])/gi,''))/1024).toFixed(0)
avaMemStr = (parseInt(avaMemStr.toUpperCase().replace(/(( )|(:)|[A-Z])/gi,''))/1024).toFixed(0)
console.log(totalMemStr);
console.log(avaMemStr);
return {total:totalMemStr,ava:avaMemStr}
}
getMemInfo();
getMemInfo返回一个对象,total是总内存,ava是剩余内存,单位为MB。
顺带自己的Github项目,一个前端跨平台方法兼容库
Sh.js github地址
集成环信聊天,目前可以发送和接接收语音、图片、文本功能
第一步首先要集成环信。要注册,我是使用的sdk3,开始的看其他人写的,但是我自己用的时候全都是坑,弄了半天也没有成功,最后干脆自己看文档,自己去实践。实践才是验证真理的唯一标准啊。
注册```javascript
var options = {
username: 用户名,
password: "密码",
nickname: ‘名称’,
appKey: WebIM.config.appkey,
success: function(result) {
//注册成功;
console.log(JSON.stringify(result));
mui.toast('注册成功');
},
error: function(e) {
//注册失败;
console.log(JSON.stringify(e));
mui.toast('注册失败:' + e.error);
},
apiUrl: WebIM.config.apiURL
};
var conn = new WebIM.connection();
conn.registerUser(options);
然后就是集成聊天了看代码首先写布局,这里大家要是写聊天的话一般都应该把布局写完了,我就在这里不写了。这个是我们接收 消息```javascript
/**
* 环信监听获取信息
*/
conn.listen({
onOpened: function(message) { //连接成功回调
// 如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息
// 手动上线指的是调用conn.setPresence(); 如果conn初始化时已将isAutoLogin设置为true
// 则无需调用conn.setPresence();
},
onClosed: function(message) {}, //连接关闭回调
onTextMessage: function(message) {
console.log(JSON.stringify(message));
console.log(JSON.stringify(message.data));
var msg = message.data;
pushMessageToJson('receiver', 'text', msg);
msgShow('receiver', 'text', msg);
msgScrollTop();
}, //收到文本消息
onEmojiMessage: function(message) {}, //收到表情消息
onPictureMessage: function(message) {
console.log(JSON.stringify(message));
var options = {
url: message.url
};
options.onFileDownloadComplete = function(data) {
console.log(JSON.stringify(data));
console.log(data);
}
options.onFileDownloadError = function() {
// 图片下载失败
console.log('Image download failed!');
};
WebIM.utils.download.call(conn, options);
msgShow('receiver', 'img', options.url);
pushMessageToJson("receiver", "img", options.url);
msgScrollTop();
}, //收到图片消息
onCmdMessage: function(message) {}, //收到命令消息
onAudioMessage: function(message) {}, //收到音频消息
onLocationMessage: function(message) {}, //收到位置消息
onFileMessage: function(message) {}, //收到文件消息
onVideoMessage: function(message) {
var node = document.getElementById('privateVideo');
var option = {
url: message.url,
headers: {
'Accept': 'audio/mp4'
},
onFileDownloadComplete: function(response) {
var objectURL = WebIM.utils.parseDownloadResponse.call(conn, response);
node.src = objectURL;
},
onFileDownloadError: function() {
console.log('File down load error.')
}
};
WebIM.utils.download.call(conn, option);
}, //收到视频消息
onPresence: function(message) {}, //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息
onRoster: function(message) {}, //处理好友申请
onInviteMessage: function(message) {}, //处理群组邀请
onOnline: function() {}, //本机网络连接成功
onOffline: function() {}, //本机网络掉线
onError: function(message) {}, //失败回调
onBlacklistUpdate: function(list) { //黑名单变动
// 查询黑名单,将好友拉黑,将好友从黑名单移除都会回调这个函数,list则是黑名单现有的所有好友信息
console.log(list);
},
onReceivedMessage: function(message) {}, //收到消息送达服务器回执
onDeliveredMessage: function(message) {}, //收到消息送达客户端回执
onReadMessage: function(message) {}, //收到消息已读回执
onCreateGroup: function(message) {}, //创建群组成功回执(需调用createGroupNew)
onMutedMessage: function(message) {} //如果用户在A群组被禁言,在A群发消息会走这个回调并且消息不会传递给群其它成员
});
```发送文本消息var sendText = function() {
var msg = ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '<br/>');
var validateReg = /^\S+$/;
//获取键盘焦点
msgTextFocus();
if(validateReg.test(msg)) {
//显示消息
msgShow('sender', 'text', msg);
//发送文本给第三方服务器,
sendPrivateText(msg);
//todo存储信息到本地
pushMessageToJson('sender', 'text', msg);
//清空文本
ui.boxMsgText.value = "";
//恢复高度
ui.footer.style.height = '50px';
//回复输入内容
mui.trigger(ui.boxMsgText, 'input', null);
// 这一句让内容滚动起来
msgScrollTop();
} else {
mui.toast("文本消息不能为空");
}
}
发送语音消息var sendAudio = function(audioData, filename) {
var id = conn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('audio', id); // 创建图片消息
var blob = dataURLtoBlob(audioData);
var url = window.URL.createObjectURL(blob);
var input = blobToFile(blob, filename);
var urlPath = window.URL.createObjectURL(input);
var file = {
url: urlPath,
filename: '',
filetype: '',
data: input
}
var allowType = {
'mp3': true,
'amr': true,
'wmv': true
};
file.filename = input.name || '';
var index = file.filename.lastIndexOf('.');
if(index != -1) {
file.filetype = file.filename.substring(index + 1).toLowerCase();
}
if(file.filetype.toLowerCase() in allowType) {
var option = {
apiUrl: WebIM.config.apiURL,
file: file,
to: '13269698989', // 接收消息对象
roomType: false,
chatType: 'singleChat',
onFileUploadError: function() { // 消息上传失败
console.log('onFileUploadError');
},
onFileUploadComplete: function() { // 消息上传成功
console.log('onFileUploadComplete');
},
success: function() { // 消息发送成功
console.log('Success');
},
flashUpload: WebIM.flashUpload
};
msg.set(option);
conn.send(msg.body);
}
}
发送图片消息
/**
* 发送图片
* @param {Object} path
*/
var sendPrivateImg = function(imgData, path, filename, filetype) {
var id = conn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('img', id); // 创建图片消息
var blob = dataURLtoBlob(imgData);
var url = window.URL.createObjectURL(blob);
var input = blobToFile(blob, filename);
var urlPath = window.URL.createObjectURL(input);
console.log('path:' + urlPath);
var file = {
url: urlPath,
filename: filename,
filetype: filetype,
data: input
}
file.url = window.URL.createObjectURL(input);
file.filename = input.name || '';
var index = file.filename.lastIndexOf('.');
if(index != -1) {
file.filetype = file.filename.substring(index + 1).toLowerCase();
}
var allowType = {
'jpg': true,
'gif': true,
'png': true,
'bmp': true
};
if(file.filetype.toLowerCase() in allowType) {
var option = {
apiUrl: WebIM.config.apiURL,
file: file,
to: '13269698989', // 接收消息对象
roomType: false,
chatType: 'singleChat',
onFileUploadError: function() { // 消息上传失败
console.log('onFileUploadError');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
onFileUploadComplete: function() { // 消息上传成功
console.log('onFileUploadComplete');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
success: function() { // 消息发送成功
console.log('Success');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
flashUpload: WebIM.flashUpload
};
msg.set(option);
conn.send(msg.body);
}
} 第一步首先要集成环信。要注册,我是使用的sdk3,开始的看其他人写的,但是我自己用的时候全都是坑,弄了半天也没有成功,最后干脆自己看文档,自己去实践。实践才是验证真理的唯一标准啊。
注册```javascript
var options = {
username: 用户名,
password: "密码",
nickname: ‘名称’,
appKey: WebIM.config.appkey,
success: function(result) {
//注册成功;
console.log(JSON.stringify(result));
mui.toast('注册成功');
},
error: function(e) {
//注册失败;
console.log(JSON.stringify(e));
mui.toast('注册失败:' + e.error);
},
apiUrl: WebIM.config.apiURL
};
var conn = new WebIM.connection();
conn.registerUser(options);
然后就是集成聊天了看代码首先写布局,这里大家要是写聊天的话一般都应该把布局写完了,我就在这里不写了。这个是我们接收 消息```javascript
/**
* 环信监听获取信息
*/
conn.listen({
onOpened: function(message) { //连接成功回调
// 如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息
// 手动上线指的是调用conn.setPresence(); 如果conn初始化时已将isAutoLogin设置为true
// 则无需调用conn.setPresence();
},
onClosed: function(message) {}, //连接关闭回调
onTextMessage: function(message) {
console.log(JSON.stringify(message));
console.log(JSON.stringify(message.data));
var msg = message.data;
pushMessageToJson('receiver', 'text', msg);
msgShow('receiver', 'text', msg);
msgScrollTop();
}, //收到文本消息
onEmojiMessage: function(message) {}, //收到表情消息
onPictureMessage: function(message) {
console.log(JSON.stringify(message));
var options = {
url: message.url
};
options.onFileDownloadComplete = function(data) {
console.log(JSON.stringify(data));
console.log(data);
}
options.onFileDownloadError = function() {
// 图片下载失败
console.log('Image download failed!');
};
WebIM.utils.download.call(conn, options);
msgShow('receiver', 'img', options.url);
pushMessageToJson("receiver", "img", options.url);
msgScrollTop();
}, //收到图片消息
onCmdMessage: function(message) {}, //收到命令消息
onAudioMessage: function(message) {}, //收到音频消息
onLocationMessage: function(message) {}, //收到位置消息
onFileMessage: function(message) {}, //收到文件消息
onVideoMessage: function(message) {
var node = document.getElementById('privateVideo');
var option = {
url: message.url,
headers: {
'Accept': 'audio/mp4'
},
onFileDownloadComplete: function(response) {
var objectURL = WebIM.utils.parseDownloadResponse.call(conn, response);
node.src = objectURL;
},
onFileDownloadError: function() {
console.log('File down load error.')
}
};
WebIM.utils.download.call(conn, option);
}, //收到视频消息
onPresence: function(message) {}, //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息
onRoster: function(message) {}, //处理好友申请
onInviteMessage: function(message) {}, //处理群组邀请
onOnline: function() {}, //本机网络连接成功
onOffline: function() {}, //本机网络掉线
onError: function(message) {}, //失败回调
onBlacklistUpdate: function(list) { //黑名单变动
// 查询黑名单,将好友拉黑,将好友从黑名单移除都会回调这个函数,list则是黑名单现有的所有好友信息
console.log(list);
},
onReceivedMessage: function(message) {}, //收到消息送达服务器回执
onDeliveredMessage: function(message) {}, //收到消息送达客户端回执
onReadMessage: function(message) {}, //收到消息已读回执
onCreateGroup: function(message) {}, //创建群组成功回执(需调用createGroupNew)
onMutedMessage: function(message) {} //如果用户在A群组被禁言,在A群发消息会走这个回调并且消息不会传递给群其它成员
});
```发送文本消息var sendText = function() {
var msg = ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '<br/>');
var validateReg = /^\S+$/;
//获取键盘焦点
msgTextFocus();
if(validateReg.test(msg)) {
//显示消息
msgShow('sender', 'text', msg);
//发送文本给第三方服务器,
sendPrivateText(msg);
//todo存储信息到本地
pushMessageToJson('sender', 'text', msg);
//清空文本
ui.boxMsgText.value = "";
//恢复高度
ui.footer.style.height = '50px';
//回复输入内容
mui.trigger(ui.boxMsgText, 'input', null);
// 这一句让内容滚动起来
msgScrollTop();
} else {
mui.toast("文本消息不能为空");
}
}
发送语音消息var sendAudio = function(audioData, filename) {
var id = conn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('audio', id); // 创建图片消息
var blob = dataURLtoBlob(audioData);
var url = window.URL.createObjectURL(blob);
var input = blobToFile(blob, filename);
var urlPath = window.URL.createObjectURL(input);
var file = {
url: urlPath,
filename: '',
filetype: '',
data: input
}
var allowType = {
'mp3': true,
'amr': true,
'wmv': true
};
file.filename = input.name || '';
var index = file.filename.lastIndexOf('.');
if(index != -1) {
file.filetype = file.filename.substring(index + 1).toLowerCase();
}
if(file.filetype.toLowerCase() in allowType) {
var option = {
apiUrl: WebIM.config.apiURL,
file: file,
to: '13269698989', // 接收消息对象
roomType: false,
chatType: 'singleChat',
onFileUploadError: function() { // 消息上传失败
console.log('onFileUploadError');
},
onFileUploadComplete: function() { // 消息上传成功
console.log('onFileUploadComplete');
},
success: function() { // 消息发送成功
console.log('Success');
},
flashUpload: WebIM.flashUpload
};
msg.set(option);
conn.send(msg.body);
}
}
发送图片消息
/**
* 发送图片
* @param {Object} path
*/
var sendPrivateImg = function(imgData, path, filename, filetype) {
var id = conn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('img', id); // 创建图片消息
var blob = dataURLtoBlob(imgData);
var url = window.URL.createObjectURL(blob);
var input = blobToFile(blob, filename);
var urlPath = window.URL.createObjectURL(input);
console.log('path:' + urlPath);
var file = {
url: urlPath,
filename: filename,
filetype: filetype,
data: input
}
file.url = window.URL.createObjectURL(input);
file.filename = input.name || '';
var index = file.filename.lastIndexOf('.');
if(index != -1) {
file.filetype = file.filename.substring(index + 1).toLowerCase();
}
var allowType = {
'jpg': true,
'gif': true,
'png': true,
'bmp': true
};
if(file.filetype.toLowerCase() in allowType) {
var option = {
apiUrl: WebIM.config.apiURL,
file: file,
to: '13269698989', // 接收消息对象
roomType: false,
chatType: 'singleChat',
onFileUploadError: function() { // 消息上传失败
console.log('onFileUploadError');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
onFileUploadComplete: function() { // 消息上传成功
console.log('onFileUploadComplete');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
success: function() { // 消息发送成功
console.log('Success');
plus.nativeUI.closeWaiting();
msgScrollTop();
},
flashUpload: WebIM.flashUpload
};
msg.set(option);
conn.send(msg.body);
}
} 收起阅读 »
mui 下拉刷新回到头部位置解决办法
//自动滚动到头部位置#refreshContainer = '你的滚动区域id',scrollTo(x,y,'滚动时间毫秒数');
mui('#refreshContainer').pullRefresh().scrollTo(0, 0, 500);
//自动滚动到头部位置#refreshContainer = '你的滚动区域id',scrollTo(x,y,'滚动时间毫秒数');
mui('#refreshContainer').pullRefresh().scrollTo(0, 0, 500);
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
群里面一个朋友让我帮忙翻译的
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
可以用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中,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
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实现深拷贝
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;
}; 收起阅读 »




