JavaScript对象克隆
浅拷贝
浅拷贝引用值是要相互影响的,因为它是栈内存之间的赋值,赋值的是地址,一个更改,其他的都要更改。
var obj = {
name :'abc',
age : 123,
sex : 'female',
card : ['visa','master']//存在一个引用值
}
var obj1 = {}
function clone(origin,target) {
var target = target || {};//防止没有提前新建一个空对象
for(var prop in origin){
target[prop] = origin[prop];
}
}
clone(obj,obj1);
深度拷贝
深拷贝就是无论是原始值还是引用值,修改后彼此相互不影响。
遍历对象 for(var prop in obj)(for in也可以遍历数组(数组也是特殊类型的对象))
1,判断是不是原始值 typeof() object—>引用值
2,判断是数组还是对象 constructor,instanceof,toString
3,建立相应的数组或者对象
4,递归
// JavaScript Document
var obj = {
name :'abc',
age : 123,
sex : 'female',
card : ['visa','master']//存在一个引用值
}
var obj1 = {}
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[Object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(origin[prop] !== "null" && typeof(origin[prop]) == 'object'){
// if(toStr.call(origin[prop]) == arrStr){
// target[prop] = [];
// }else{
// target[prop] = {};
// }
//上面的ifelse条件判断可以用下面的三目运算符代替:
target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop],target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
deepClone(obj,obj1);
更改引用值之间相互不影响
数组
数组的两种创建方式
-
var arr = [];//字面量(内部还是new Array()的方式)
-
var arr = new Array();
改变原数组方法
- push()
功能:在数组的最后一位新增一个或多个数据,并且返回新数组的长度,会改变原来的数组
注意:push()方法返回的是数据是新数组的长度,它增加的数据可以是一个也可以是多个,可以理解为增加一连串的数据
模拟系统调用数组的push方法:
Array.prototype.push = function(){
for(var i = 0; i <arguments.length; i++){
this[this.length] = arguments[i];
}
return this.length;
}
- pop()
功能:删除数组的最后一位,并且返回删除的数据,会改变原来的数组
注意:带了参数也没得用,还是删除最后一个
- shift()
功能:删除数组的第一位数据,并且返回新数组的长度,会改变原来的数组
带不带参数都是一样的,只剪切数组的第一个元素
- unshift
在数组的第一位添加元素。返回的是数组的长度
- sort
数组直接调用sort()方法,它是 按照ASCII排序的。
ASCII比较大小的时候,是比较两个数中的第一个字符。
- 常见ASCII码的大小规则,0-9<A-Z<a-z:
- 数字比字母要小。如 “7”<“F”
- 数字0比数字9要小,并按0到9顺序递增。如 “3”<“8”
- 字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z”
- 同个字母的大写字母比小写字母要小32。如“A”<“a”
记住几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为48;
让数组按照大小排序就要实现sort高阶函数的使用
1,必须写两个形参
2,看返回值
1)当返回值为负数时,那么前面的是放在前面
2)当返回值为正数时,那么后面的是放在前面
3)为0,不动
var arr= [1,3,5,4,10];
arr.sort(function (a,b) {
if(a > b){
return 1;
}else{
return -1;
}
});
console.log(arr);//[1,3,4,5,10]
上面高阶sort函数的实现可以简化为下面这种形式:
arr.sort(function (a,b) {
return a-b;//升序
// return b-a;//降序
});
它是这样的执行过程,数组里面的数首先传a= 1,b = 3,1和3比,然后1,4。1,5。1,10 。3,4。3,5 。3,10 。4,5 。4,10 。5,10 。
这样多次调用里面的函数进行比较(就想冒泡排序一样)
因为 if(a > b){return 1;}就可以为 if(a - b > 0){return 1;}—>return a - b
因为这个时候a> b,只要返回整数就可以了
然后再else里面就是a<b返回负数,那么return a-b肯定是负数。所有它们都返回的是a-b,直接一下子就返回a - b
给一个有序的数组排序,让它每一次执行都是乱序
var arr = [1,2,3,4,5,6,7];
arr.sort(function () {
return Math.random() - 0.5;
});
console.log(arr);
-
发现每执行一次,arr数组里面的顺序都不一样。
-
因为 Math.random()返回的结果是(0,1),这样减去0.5,那么它有为正的概率和为负的概率是一半一半。
-
根据sort函数的原理,如果返回的是正数,那么后面的和前面的调个位置,如果返回的是负数或者0,那么前面的还是在前面
将数组里面的按字节长度排序
var arr = ['ac','nsfauigf','网络','osy题词','wdfewfef','addasd'];
function retBytes(str) {
var num = str.length;
for(var i = 0; i <str.length; i++){
if(str.charCodeAt(i) > 255){
num++;
}
}
return num;
}
arr.sort(function(a,b) {
return retBytes(a) - retBytes(b);
})
- reverse
数组反转
- splice
splice ( 从第几位开始截取 , 截取多少位 , 在截取点处添加什么)
负数的实现原理是这样的:pos += pos > 0 ? 0 : this.length;也就是倒数的第几位
首先判断,如果是负数的话那就是负数加上长度就等于正数的第几位(从0开始)
不改变原数组方法
- concat
var arr = [1,2,3]
var arr1 = [4,5,6]
var arr3 = arr.concat(arr1);//arr3 : [ 1, 2, 3, 4, 5, 6 ]
例题:
把这一串字符串链接起来,不用+号的方式(因为字符串是原始值,存在栈内存里面的,这样来回在栈内取元素很浪费时间。 )。
var str1 = "nsd";
var str2 = "sdsfd";
var str3 = "fadf";
var arr = [str1,str3];//数组是散列结构
console.log(arr.join(""));//nsdfadf
//console.log(arr.join());// nsd,fadf
join()里面为空就是用逗号连起来。
数组的方法join 和 字符串的方法split是互逆的
var arr= [1,2,3,4,5];
var str = arr.join("-"); // "1-2-3-4-5"
var shuzu =str.split("-");//[ "1", "2", "3", "4", "5" ]
- toString
var arr = [1,2,3,4]
console.log(arr.toString());//1,2,3,4
- slice
返回值是截取的值,不改变原数组。
-
slice两个参数(从该位为开始截取,截取到该位)
-
slice一个参数(从该位开始截取到后面所有都截取)
-
slice没有参数(整个截取)
类数组
- 可以利用属性名模拟数组的特性
- 可以动态的增加length属性
- 如果强行让类数组调用push方法,则会根据length属性值的位置进行属性值的扩充。
类数组举例
1,arguments
2,一个对象:属性要为索引(数字)属性,必须有length属性,最好加上push
类数组里面最关键的是length属性,类数组中push实现的原理
Array.prototype.push = function (target) {
obj[obj.length] = target;
obj.length++;
}
var obj = {
"2" : "a",
"3" : "b",
"length" : 2,
"push" : Array.prototype.push
}
obj.push('c');
obj.push('d');
console.log(obj);
//Object { 2: "c", 3: "d", length: 4, push: push() }
var obj = {
"1" : "a",
"2" : "c",
"3" : "d",
"length" : 3,
"push" : Array.prototype.push
}
obj.push('b');
console.log(obj);
//Object { 1: "a", 2: "c", 3: "b", length: 4, push: push() }
var obj = {
"0" : 'a',
"1" : 'b',
"2" : 'c',
name :"abc",
age : 123,
length : 3,
push : Array.prototype.push,
splice :Array.prototype.splice
}
类数组的好处就是把数组和对象的好处拼到一起,
但是并不是所有的数组方法都能用,除非你自己添加
封装typeof方法
1,分两类,原始值和引用值
2.区分引用值
function type (target){
var ret = typeof(target);
var template = {
"[object Array]" : "array",
"[object Object]" : "object",
"[object Number]" : "number-object",
"[object String]" : "string-object",
"[object Boolean]" : "boolean-object"
}
if(target === null){
return "null";
}else if(ret == 'object'){//引用值:数组,对象,包装类
var str = Object.prototype.toString.call(target);
return template[str];//属性值
}else{
return ret;//原始值:直接用typeof来区分
}
}
数组去重(要求在原型链上编程)
var arr = [1,1,1,1,2,2,2,2,2];
//利用对象的一个属性只能对应一个值的特性来去重
//var obj = {
// "1" : 'abc',
// "2" : 'abc,
//}
"1"-->undefined
"1"-->"abc"
"2"-->undefined
"2"-->"abc"
//新建一个对象,遍历数组里面的每一个值拿来当做对象的一个属性,如果该属性已经存在了值,那就跳过,继续找下一个值。
//看它是不是为对象在对象里面已经有了值,如果没有值,那就把数组里面的这个值作为该对象的一个新的属性。
//最后输出对象的所有属性就是去掉数组里面重复的值。
Array.prototype.unique = function () {
var temp = {}; //新建一个空对象
var arr = []; //新建一个空数组,用来返回 去重后的结果
var len = this.length;
for(var i = 0; i < len; i++){
if (!temp[this[i]] ) { //this[i]表示arr[i]表示数组里面的每一个值
//temp[this[i]]表示对象里面的这个属性(数组里面的值)的值是不是undefined,如果是undefined,就说明这要添加到该对象里面为一个新的属性。
//所以这个地方就是!temp[this[i]],表述如果它是!undefined == true就要给它赋值,然后push到去重数组里面去。
temp[this[i]] = 'abc';
arr.push(this[i]);
}
}
return arr;
}
ES5严格模式
Javascript 的保留关键字不可以用作变量、标签或者函数名。有些保留关键字是作为 Javascript 以后扩展使用。ES5较ES3新添加了几个保留关键字:
1)class:类,ES6引入
2)const:常量,ES6引入
3)enum:枚举类型
4)extends:类继承,ES6引入
5)import:模块导入,ES6引入
6)export:模块导出,ES6引入
7)super:调用父类的构造函数,ES6引入
浏览器兼容性
1)ES3,可以认为所有浏览器都支持;
2)ES5,现代浏览器都支持(>=IE9), IE9不支持严格模式。
在try里面的发生错误,不会执行错误后的try里面的代码
- try 测试代码块的错误。
- catch 语句处理错误。
- throw 创建并跑出错误。
try
{
//在这里运行代码
抛出错误
}
catch(err)
{
//在这里处理错误
}
请输出一个 5 到 10 之间的数字:
<input id="demo" type="text">
<button type="button" onclick="myFunction()">测试输入</button>
<p id="mess"></p>
</body>
</html>
<script type="text/javascript">
function myFunction(){
try{
var x=document.getElementById("demo").value; 取元素的值
if(x=="") throw "值为空"; 根据获取的值,抛出错误
if(isNaN(x)) throw "不是数字";
if(x>10) throw "太大";
if(x<5) throw "太小";
}
catch(err){
var y=document.getElementById("mess"); 抓住上面throw抛出的错误,给p标签显示
y.innerHTML="错误:" + err + "。";
}
}
</script>
Error.name的六种值对应的信息
-
EvalError : eval()的使用与定义不一致
-
RangeError : 数组越界
-
ReferenceError : 非法或不能识别的引用数值(一般没有定义就使用就会报ReferenceError)
-
SyntaxError :发生语法解析错误
-
TypeError : 操作数类型错误
-
URIError : URI处理函数使用不当
es5.0严格模式
启动es5.0的严格模式用"use strict" ,放在逻辑的最顶端(全局严格模式),或者放在局部的最顶端让它部分启用es5.0的严格模式(局部函数内的严格模式)。
这个时候和es3.0产生冲突的部分全部使用es5.0的严格模式
"use strict"就是一行字符串。不会对不兼容严格模式的浏览器产生影响
不支持with,arguments.callee,function.caller
var obj={
name:"obj"
}
var name='window';
function test(){
var name="scope";
with(obj){
console.log(name);
}
}
test()
with它会改变作用域链,with(obj)里面跟的obj会被放在作用域的最顶端,那么首先查找作用域链的时候就会找obj里面的。因为with的功能太强大,消耗大量的资源。所以在严格模式里面不准用
变量赋值前必须声明。
var a = b = 3;//会报错,b报错
局部this必须被赋值(Person.call(null/undefined)赋值什么就是什么) ,就是说预编译this不再指向window,它为undefined,在全局范围里面this依旧是指向window。
es5拒绝重复的属性 和参数
eval是魔鬼
eval 能该表作用域
数组去重
第一种方法用ES6里面的Set特性来去重
var arr = [4,7,9,4,7,8,9,4];
var set = new Set();
for(var i = 0; i< arr.length; i++){
set.add(arr[i]);
}
console.log(set);
第二种方法用一个新的数组里面indexOf属型的性质来去重
(indexOf返回的就是字符出现在字符串的位置,或者判断字符串出现在数组的位置,返回数组的下标)
var newArr=[];
for(var i = 0; i < arr.length; i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i]);
}
}
console.log(newArr);
第三种方法就一句就可以实现去重
console.log(Array.from(new Set(arr)));
寻找落单
第一种方法:用ES6里面的Set去重留下落单的那个。
var arr = [1,2,3,4,5,1,2,3,4];
var set = new Set();
for(var i = 0; i <arr.length; i++){
if(set.has(arr[i])){
set.delete(arr[i]);
}else{
set.add(arr[i]);
}
}
console.log(set);//结构是一个数组: Set(1) {5}
console.log(...set);//5 用展开运算符把数组展开
第二种方法:根据对象的属性是不允许有重复的这一特性。
var arr = [1,2,3,4,5,1,2,3,4];
var obj = {};
for(var i = 0; i < arr.length; i++){
if(obj[arr[i]]){
delete obj[arr[i]];
}else{
obj[arr[i]] = true;
}
}
console.log(Object.keys(obj)[0]);//通过这种方法拿到对象的key
第三种方法用到了异或^
首先你要知道0和任何数异或都等于任何数,然后任何数和自己异或都为0,
因为相同为0,不同为1,它也存在交换律和结合律。
所以用它在找那个落单的数,是一定可以找到的。
var arr = [1,2,3,4,5,1,2,3,4];
var result = 0
for(var i = 0; i < arr.length; i++){
result ^=arr[i];
}
console.log(result);