1. 创建对象的三种方法
- 用{}字面量创建:var 对象名 = {};
- 用new的方式创建:var 对象名 = new Object();
- 以什么对象为原型创建一个新对象:Object.create(对象名);
注意!!!
- Object.create(对象名)方法的参数只能是对象或null。
- 如果使用Object.create(null)作为原型创建对象,无属性,没有原型链,所以不能使用对象的任何方法。
- Object.create(对象名)方法可以设置原型链。
案例1:
// 方法一:
var o = {};
// 方法二:
var o = new Object();
// 方法三: Object.create() 以什么对象为原型创建一个新对象
var o = { a: 1, b: 2 };
var o1 = Object.create(o);
o1.c = 3;
o1.d = 4;
console.log(o1);
案例2:
var o = Object.create(null);
console.log(o);//无属性
console.log(o + "1");//报错
案例3:
var o1 = { a: 1 };
var o2 = Object.create(o1);
o2.b = 2;
o2.z = 10;
var o3 = Object.create(o2);
o3.c = 3;
o3.z = 100;
var o4 = Object.create(o3);
o4.d = 4;
console.log(o4);
2. 对象属性
对象属性分为对象属性和原型属性。
当获取对象属性时,首先查找对象的对象属性,如果没有则向下继续查找原型属性,找到最近的原型属性返回。
当给对象设置属性时,不能设置原型属性,只能设置对象的对象属性。原型对象的引用地址和对应的对象是一致。
案例1:
var o1 = { a: 1 };
var o2 = Object.create(o1);
o2.b = 2;
o2.z = 10;
var o3 = Object.create(o2);
o3.c = 3;
o3.z = 100;
var o4 = Object.create(o3);
o4.d = 4;
console.log(o4);
// 对象属性中分为对象属性和原型属性
// 当获取对象属性时,首先查找对象的对象属性,如果没有则向下继续查找原型属性,找到最近的原型属性返回
console.log(o4.d);
console.log(o4.b);
console.log(o4.z);
// 当给对象设置属性时,不能设置原型属性,只能设置对象的对象属性
o4.z = 1000;
console.log(o4);
// 原型对象的引用地址和对应的对象是一致
o3.z = 1;
console.log(o4);
3. 获取原型链的方法
获取原型链的方法:
- 对象名.__proto__ :禁止使用__proto__
- Object.getPrototypeOf(对象名)
设置原型链的方法:
- 对象名.__proto__ = 对象名;:禁止使用__proto__
- Object.setPrototypeOf(目标对象, 作为原型链的对象);
Object.create(对象名);
案例1:
var o1 = { a: 1 };
var o2 = Object.create(o1);
o2.b = 2;
o2.z = 10;
var o3 = Object.create(o2);
o3.c = 3;
o3.z = 100;
var o4 = Object.create(o3);
o4.d = 4;
// 原型对象的引用地址和对应的对象是一致
// 获取原型链 禁止使用__proto__
console.log(o4.__proto__ === o3);
// 获取原型链
console.log(Object.getPrototypeOf(o4));
案例2:
var o1 = { a: 1 };
var o2 = { b: 2 };
// 禁止使用
// o2.__proto__ = o1;
// 设置原型链
Object.setPrototypeOf(o2, o1);
console.log(o2);
案例3: 使用两种方法让第一个对象{a:1}作为第二个对象{b:2}的原型,第二个对象{b:2}作为第三个对象 {c:3}的原型,形成原型链。
// 方法一:
var o1 = { a: 1 };
var o2 = Object.create(o1);
o2.b = 2;
var o3 = Object.create(o2);
o3.c = 3;
console.log(o3);
-----------------------------------------------
// 方法二:
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
Object.setPrototypeOf(o2, o1);
Object.setPrototypeOf(o3, o2);
console.log(o3);
4. 迭代器和判断属性是否是当前对象的对象属性
遍历方法:
- for in 遍历:遍历对象时,可以遍历对象中的对象属性和原型链属性,不能遍历不可枚举属性。
- for of 遍历 :只能遍历迭代器。不能遍历对象。对象要使用for of必须使用迭代器。
判断这个属性是不是当前对象的对象属性的方法:
- Object.hasOwn(对象名,属性名):判断这个属性是不是当前对象的对象属性。
Object.hasOwn(数组名,所以下标):判断数组的下标是不是空元素。
2. 对象.hasOwnProperty(属性名) :判断这个属性是不是当前对象的对象属性。
迭代器:
- Object.entries(对象名):将对象转换为数组迭代器(二维数组)。例如:[["a",1],["b",2]]。
- Object.fromEntries(数组迭代器) :将数组迭代器(二维数组)转换为对象。
案例1:
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
Object.setPrototypeOf(o2, o1);
Object.setPrototypeOf(o3, o2);
// 遍历对象时,可以遍历对象中的对象属性和原型链属性,不能遍历不可枚举属性
// Object.hasOwn(对象,属性)判断这个属性是不是当前对象的对象属性
// 对象.hasOwnProperty(属性) 判断这个属性是不是当前对象的对象属性
for (var key in o3) {
console.log(key);
console.log(key, Object.hasOwn(o3, key));
console.log(o3.hasOwnProperty(key));
}
案例2:
var arr = ["a", "b", , "c"];
// Object.hasOwn(数组,下标)判断数组的下标是不是空元素
console.log(Object.hasOwn(arr, 2));
console.log(arr.hasOwnProperty(2));
案例3:
var o = { a: 1, b: 2, c: 3 };
console.log(Object.entries(o));
// [["a",1],["b",2],["c",3]]
// 对象不是迭代器,本身不能使用for of
for (var [key, value] of Object.entries(o)) {
console.log(key, value);
}
案例4:
// 将迭代器转换为对象
var o = Object.fromEntries([["a", 1], ["b", 2]])
console.log(o);
5. 对象浅复制
Object.assign(目标对象,...源对象) :将源对象中所有可枚举的对象属性复制到目标对象的对象属性上,如果源对象中的属性有重复,后面的会覆盖前面的。源对象可以是多个。 这个方法最后返回目标对象。
案例1:
var o = { a: 1, b: 2 };
var o1 = Object.create(o);
o1.c = 3;
o1.d = 4;
var o2 = { e: 4, f: 5 };
Object.assign(o2, o1);
console.log(o2);
案例2:
var o1 = { a: 1 };
var o2 = { b: 2, z: 1 };
var o3 = { c: 3, z: 2 };
var o4 = { d: 4 };
Object.assign(o4, o1, o2, o3)
console.log(o4);
案例3:
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = Object.assign({}, o1, o2);
console.log(o3);
// {...}这种写法一般用于创建一个新对象
// Object.assign() 用于已有对象复制使用
var o4 = { ...o1, ...o2 };
console.log(o4);
6. 冻结、密封、禁止扩展对象
- Object.freeze(对象名):冻结对象。对象不能删除属性,不能添加新属性,不能修改属性值。在js中没有enum枚举,所以使用该方法可以属性enum 枚举。
- Object.isFrozen(对象名):当前对象是否被冻结。
- Object.seal(对象名): 密封对象,可以修改属性,但是不能删除和添加新属性。
- Object.isSealed(对象名):判断是否被密封。
- Object.preventExtensions(对象名) : 禁止扩展对象,可以删除属性,可以修改属性,但是不能添加新属性。
- Object.isExtensible(对象名) : 判断是否被禁止扩展对象。false代表禁止扩展, true表示可扩展。
案例1:
// 冻结对象 对象不能删除属性,不能添加新属性,不能修改属性值
var o = { a: 1, b: 2 };
Object.freeze(o)
// 删除对象属性
delete o.a;
console.log(o);
// 修改对象属性
o.a = 10;
console.log(o);
// 添加对象属性
o.c = 10;
console.log(o);
案例2:
const COLOR = Object.freeze({ RED: "RED", BLUE: "BLUE", YELLOW: "YELLOW", GREEN: "GREEN" })
// 当前对象是否被冻结
console.log(Object.isFrozen(COLOR));//true
案例3:
var o1 = Object.seal({ a: 1, b: 2 })
o1.a = 10;
console.log(o1);
// 判断是否被密封
console.log(Object.isSealed(o1));
案例4:
// 禁止扩展 可以删除属性,可以修改属性,但是不能添加新属性
var o1 = Object.preventExtensions({ a: 1, b: 2 })
delete o1.a;
o1.b = 10;
o1.c = 20;
console.log(o1);
// false代表禁止扩展 true表示可扩展
console.log(Object.isExtensible(o1));
7. 添加属性并定义该属性特性的方法
1. Object.defineProperty(对象, "属性名", 属性描述对象) :定义单个属性。该方法不能删除,在严格模式下,删除会报错;不能重新定义;当属性定义为不可枚举时,for in是不能遍历到该属性的,但能拿到该属性的值;当该属性定义为不可修改时,该属性不能修改,如果在严格模式下,删除会报错。
属性描述对象中的属性:
- enumerable:是否可枚举。布尔值。
- configurable: 是否可删除或者重新设置属性描述对象。布尔值。
- writable:是否可写,是否可以修改。布尔值。
- value : 如果value是非函数,则这是一个属性值,如果value是一个函数,则这是一个方法。value:function(){...}或value(){...}都表示value是一个函数。
该方法的使用模板1:
Object.defineProperty(对象名, "属性名", {
enumerable: true,
configurable: true,
writable: true,
value: 属性值,
})
该方法的使用模板2:
Object.defineProperty(对象名, "属性名",{
enumerable: true,
configurable: true,
set(value) {
},
get() {
}
})
特性写法:这个写法默认该属性不可删除,不可修改,不可枚举
Object.defineProperty(对象名, "属性名", {
// 默认如果不写则为false
value: 属性值
})
2. Object.defineProperties(对象,对象多属性描述对象):定义多个属性。
该方法使用模板:
Object.defineProperties(对象名, {
// 属性名:属性描述对象
a: {
enumerable: true,
configurable: true,
writable: false,
value: 1
},
b: {
value: 2
}
})
案例1:
// 属性
var o = { a: 1, b: 2 };
Object.defineProperty(o, "c", {
enumerable: true,
configurable: true,
writable: true,
value: 3,
})
console.log(o);
案例2:
var o = { a: 1, b: 2 };
Object.defineProperty(o, "play", {
enumerable: true,
configurable: true,
writable: true,
// value:function(){}
value() {
console.log("play");
}
})
console.log(o);
案例3:
var o = { a: 1, b: 2 };
Object.defineProperty(o, "c", {
configurable: false,
writable: true,
enumerable: true,
value: 3
})
// 不能删除
delete o.c;
console.log(o);
// 不能重新定义
Object.defineProperty(o, "c", {//报错
configurable: true,
writable: true,
enumerable: true,
value: 3
})
console.log(o);
案例4:属性c不能被遍历出来。
var o = { a: 1, b: 2 };
Object.defineProperty(o, "c", {
configurable: true,
enumerable: false,//不可枚举
writable: true,
value: 3
})
console.log(o);
for (var key in o) {
console.log(key);
}
案例5:
var o = { a: 1, b: 2 };
Object.defineProperty(o, "c", {
configurable: true,
enumerable: true,
writable: false,//不可修改,不可写
value: 3
})
o.c = 10;
console.log(o.c);//3
案例6:定义多个属性
var o = {};
Object.defineProperties(o, {
// 属性名:属性描述对象
a: {
enumerable: true,
configurable: true,
writable: false,
value: 1
},
b: {
value: 2
}
})
console.log(o);
案例7:
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
console.log(o);
8. 遍历不可枚举属性的方法
for in遍历:不靠谱,只能遍历可枚举,并且只能是字符的串属性名。
- Object.getOwnPropertyNames(对象名) :获取对象的所有可枚举和不可枚举的字符串属性名。
- Object.getOwnPropertySymbols(对象名):获取对象的所有可枚举和不可枚举的Symbol属性名。
- Reflect.ownKeys(对象名): Reflect 针对Object对象的所有方法反射方法集,获取到对象中所有的可枚举和不可枚举的任意类型属性名。
案例1:
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
console.log(o);
// 不靠谱 只能遍历可枚举,并且字符串属性名
// for (var key in o) {
// console.log(key, o[key]);
// }
// 获取对象的所有可枚举和不可枚举的字符串属性名
var arr = Object.getOwnPropertyNames(o);
// 获取对象的所有可枚举和不可枚举的Symbol属性名
var arr1 = Object.getOwnPropertySymbols(o);
console.log(arr, arr1);
案例2:遍历
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
console.log(o);
// 合并写法一;
var arr = Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))
for (var i = 0; i < arr.length; i++) {
console.log(arr[i], o[arr[i]]);
}
-----------------------------------------------------------------------------------------
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
console.log(o);
// 合并写法二;
var arr = [...Object.getOwnPropertyNames(o), ...Object.getOwnPropertySymbols(o)]
for (var i = 0; i < arr.length; i++) {
console.log(arr[i], o[arr[i]]);
}
-----------------------------------------------------------------------------------------
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
console.log(o);
// 获取到对象中所有的可枚举和不可枚举的任意类型属性名
// Reflect 针对Object对象的所有方法反射方法集
var arr = Reflect.ownKeys(o);
for (var i = 0; i < arr.length; i++) {
console.log(arr[i], o[arr[i]]);
}
9. 获取对象属性的描述对象
- Object.getOwnPropertyDescriptor(对象名, "属性名") :获取对象属性的描述对象。
- Object.getOwnPropertyDescriptors(对象名) : 获取对象属性上所有的描述对象。
案例1:
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
// 获取对象属性的描述对象
var desc = Object.getOwnPropertyDescriptor(o, "c");
console.log(desc);
案例2:
var o = {
a: 1,
"b-1": 2,
[Symbol("a")]: 3
}
Object.defineProperties(o, {
c: {
value: 4
},
[Symbol("b")]: {
writable: true,
value: 5
}
})
// 获取对象属性上所有的描述对象
var descs = Object.getOwnPropertyDescriptors(o);
console.log(descs);
10. 对象群组方法
Object.groupBy( 数组迭代器, function (元素, 索引下标) { }):遍历或分组。返回的是数组。
案例1:
var o = { a: 1, b: 2 };
Object.groupBy(Object.entries(o), function (item, index) {
console.log(item, index);
})
案例2:
var arr = ["a", "b", "c"];
Object.groupBy(arr, function (item, index) {
console.log(item, index);
})
案例3:
var list = [
{ a: 1, type: 0, price: 1000 },
{ a: 2, type: 1, price: 2000 },
{ a: 3, type: 0, price: 2000 },
{ a: 4, type: 1, price: 3000 },
{ a: 5, type: 1, price: 4000 },
{ a: 6, type: 0, price: 5000 },
]
var list1 = Object.groupBy(list, (item) => item.type);
console.log(list1);
11. isNaN()、Number.isNaN()和Object.is()的区别
- isNaN() :该方法会隐式转化,在判断是不是NaN。
- Number.isNaN():该方法直接判断NaN。不会隐式转化。
- Object.is():该方法是判断两个值是否绝对相等(===),不会隐式转化。能判断与NaN是否绝对相等(===)。
案例1:
// isNaN()
// Number.isNaN()不会隐式转换
// Object.is() 与===相类似 判断是否绝对相等
var a = 1;
var b = "1";
console.log(Object.is(a, b));//false
// ===永远不等于NaN
console.log(Number("a") === NaN);
console.log(Object.is("a", NaN));
12. 获取对象key-valus的方法
Object.keys(对象名) :只能获取到对象中所有可枚举的字符串属性名组成数组。
Object.values(对象名) :只能获取到对象中所有可枚举的字符串属性名对应属性值组成数组。
案例:
var obj = { a: 1, b: 2, c: 3 };
// 只能获取到对象中所有可枚举的字符串属性名组成数组
console.log(Object.keys(obj));
// 只能获取到对象中所有可枚举的字符串属性名对应属性值组成数组
console.log(Object.values(obj));
13. 对象的getter和setter
getter和setter是访问器属性。
希望可以像属性一样使用=号赋值,并且在赋值后还可以执行多条语句。
案例:本案例中,怎么做到重新执行 obj.init();就能到达重新渲染页面的效果。
var obj = {
list: [1, 2, 3, 4],
init() {
document.body.innerHTML = `
<ul>
${this.list.reduce((v, t) => v + `<li>${t}</li>`, "")}
</ul>
`
}
}
obj.init();
//怎么做到重新执行 obj.init();就能到达重新渲染页面的效果
obj.list = ["a", "b", "c", "d"]
obj.init();
解决方法:定义对象时,直接加入set和get方法。
- set 方法:有且仅有一个参数,并且不允许使用return返回任何值;当赋值时,执行set这个方法,参数就是赋值的结果,可以在赋值后执行多条语句;如果只写set 不写get,表示这个属性只能设置值,不能获取该属性,是只写属性。
- get 方法:不能有参数,并且必须return返回一个值;获取对象属性 时,执行get方法,获取最后要有一个获取的结果,所以需要return结果;如果只写get 不写set,表示这个属性只能获取值,不能设置改变值 ,是只读属性;这个get这个属性不能用来存储任何内容,需要借助第三方属性存储。
案例1:
// 定义对象时,直接加入set和get
var obj = {
_list: 1,
// set 方法 有且仅有一个参数,并且不允许使用return返回任何值
// list 这个getset属性不能用来存储任何内容,需要借助第三方属性存储
set list(value) {
this._list = value;
// console.log("set",value);
},
// get 方法 不能有参数,并且必须return返回一个值
get list() {
// console.log("get");
// return ""
return this._list;
}
}
// 和属性的赋值方法完全相同
// 当赋值时,执行set list这个方法,参数就是赋值的结果,可以在赋值后执行多条语句
// 如果只写set 不写get,表示这个属性只能设置值,不能获取该属性 只写
obj.list = "a";
// 获取对象属性 执行get list方法,获取最后要有一个获取的结果,所以需要return结果
// 如果只写get 不写set,表示这个属性只能获取值,不能设置改变值 只读
console.log(obj.list);
案例2:
var obj = {
_list: [],
// 重新给属性赋值时才会执行set
set list(value) {
if (!Array.isArray(value)) return;
this._list = value;
document.body.innerHTML = `
<ul>
${this.list.reduce((v, t) => v + `<li>${t}</li>`, "")}
</ul>
`
},
get list() {
return this._list;
}
}
obj.list = [1, 2, 3, 4]
document.addEventListener("click", () => {
obj.list = ["a", "b", "c", "d", "e"]
// obj.list.push(5);
obj.list = obj.list.concat(5);
})
案例3:div自动移动案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 50px;
height: 50px;
background-color: red;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div></div>
<script type="module">
var div = document.querySelector("div");
var obj = {
_x: 0,
_y: 0,
set x(value) {
this._x = value;
div.style.left = value + "px";
},
get x() {
return this._x
},
set y(value) {
this._y = value;
div.style.top = value + "px";
},
get y() {
return this._y;
}
}
setInterval(() => {
// set get
obj.x++;//相当于obj.x=obj.x+1
obj.y++;
}, 16)
</script>
</body>
</html>
案例4:div自动移动案例的升级版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 50px;
height: 50px;
background-color: red;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div></div>
<script type="module">
var div = document.querySelector("div");
// 如果对象已经存在,如果要给对象中添加setget属性,就必须使用defineProperty或者defineProperties
HTMLElement.prototype.a = 10;
console.log(div.a);
Object.defineProperties(HTMLElement.prototype, {
_x: {
writable: true,//可修改,不可枚举,不可删除
value: 0
},
_y: {
writable: true,//可修改,不可枚举,不可删除
value: 0
},
x: {
set(value) {
this._x = value;
this.style.left = value + "px"
},
get() {
return this._x;
}
},
y: {
set(value) {
this._y = value;
this.style.top = value + "px"
},
get() {
return this._y;
}
}
})
setInterval(() => {
div.x++;
div.y++;
}, 16)
</script>
</body>
</html>
14. 对象深复制
14.1 对象浅复制的弊端
复制对象的目的,修改对象后,不会引起源对象的改变,保留源对象的内容。
- JSON.stringify(对象名) :将对象转换为JSON格式的字符串。格式 '{"属性名":属性值}'。 属性值如果是字符串需要使用""。
- JSON.parse():把JSON格式的字符串转换为对象,返回新对象;可以用于把字符串强转为布尔类型和数值类型。
案例:将下面对象的age属性进行随机修改,修改范围在25<x<35的范围内就停止修改。
var data = {
name: "xietian",
age: 30,
sex: "男"
}
解决方法:下面的方法都是浅复制
var data = {
name: "xietian",
age: 30,
sex: "男"
}
while (1) {
// 复制对象方法一:
// var newData = {}
// for (var key in data) {
// newData[key] = data[key]
// }
// 复制对象方法二:
// var newData = Object.assign({}, data);
// var newData = { ...data };
// 复制对象方法三:
var newData = Object.keys(data).reduce((v, k) => v[k] = data[k], {})
newData.age = ~~(Math.random() * 100)
if (newData.age < data.age + 5 && newData.age > data.age - 5) {
break;
}
}
console.log(newData);
浅复制只能复制一层,遇到对象中属性值还是对象的,改变复制新对象的属性,还是会改变源对象的属性值。属性值的引用地址没有变。例如:如下图所示:
var data = {
a: 1,
b: {
c: 2
}
}
为了解决上述问题,使用要使用深复制。
14.2 对象深复制使用
14.2.1 方法一:使用lodash插件可以进行对象深复制
lodash插件的方法:
_.cloneDeep(对象名) :深复制
1. 下载lodash插件,打开集成终端输入npm i lodash。
2. 创建一个js的文件夹,在node_modules下把lodash.js放在js文件夹下,删除node_modules。
3. 在页面中引入lodash插件。
<script src="./js/lodash.js"></script>
4. 编写代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/lodash.js"></script>
</head>
<body>
<script>
var data = {
a: 1,
[Symbol("a")]: 2,
c: /a/g,
b: {
c: 3,
play() {
console.log("aa");
}
}
}
//深复制方法一:
// var newData = JSON.parse(JSON.stringify(data));
// console.log(newData);
//深复制方法二:
var newData = _.cloneDeep(data);
// 改变源对象的值,查看是否会改变新对象的值
data.b.c = 30;
console.log(newData, data);
</script>
</body>
</html>
lodash插件进行深复制也有弊端,有些数据还有引用关系。
14.2.2 方法二:封装对象深复制(重点)
遍历分为 广度遍历( for ,for in ,for of)和 深度遍历( while ,递归)。
1. 怎样做递归?递归的方式分两种。
- 方法一:先做深度遍历,在做广度遍历。
- 方法二:先做广度遍历,在做深度遍历。
案例1:二叉树的遍历
var tree = {
value: 1,
left: {
value: 2,
left: {
value: 4,
left: null,
right: null
},
right: {
value: 5,
left: null,
right: null
},
},
right: {
value: 3,
left: {
value: 6,
left: null,
right: null
},
right: {
value: 7,
left: null,
right: null
},
},
};
// 先序遍历
// function showTree(tree) {
// console.log(tree.value);
// if (tree.left) showTree(tree.left)
// if (tree.right) showTree(tree.right)
// }
// 中序遍历
// function showTree(tree) {
// if (tree.left) showTree(tree.left)
// console.log(tree.value);
// if (tree.right) showTree(tree.right)
// }
// 后序遍历
function showTree(tree) {
if (tree.left) showTree(tree.left)
if (tree.right) showTree(tree.right)
console.log(tree.value);
}
showTree(tree);
案例2:
var obj = {
a: 1,
b: {
c: [1, 2, 3, 4],
d: {
e: 3,
},
},
f: {
g: {
h: 4,
},
},
};
//方法一:先做深度遍历,在做广度遍历。
// function showDeep(obj) {
// for (var key in obj) {
// // 是对象,但不是null
// if (obj[key] && typeof obj[key] === "object") {
// showDeep(obj[key])
// } else {
// console.log(obj[key]);
// }
// }
// }
// 方法二:先做广度遍历,在做深度遍历。
function showDeep(obj) {
if (typeof obj !== "object" || !obj) {
console.log(obj);
}
for (var key in obj) {
showDeep(obj[key])
}
}
showDeep(obj);
案例3:尾递归 ,在尾部递归。
// 递归 尾递归 在尾部递归
function getSum(i = 0, sum = 0) {
i++;
if (i > 100) return sum;//只在最后一次执行
sum += i;
return getSum(i, sum);//除了最后一次返回,其他每次返回
}
var sum = getSum();
console.log(sum);
案例4:纯函数递归
var obj = {
a: 1,
b: {
c: [1, 2, 3, 4],
d: {
e: 3,
},
},
f: {
g: {
h: 4,
},
},
};
function deepClone(obj, target = {}) {
// 先广度遍历
for (var key in obj) {
// 对象的属性存在并且是对象
if (obj[key] && typeof obj[key] === "object") {
target[key] = deepClone(obj[key])
} else {
target[key] = obj[key]
}
}
return target;
}
var target = deepClone(obj);
obj.b.d.e = 1000;
console.log(target, obj);
2. 封装对象深复制(重点)
需求:将下面这个对象进行复制,下面这个对象的属性有数值,数组,集合,反射,特殊属性名,对象,BOM元素,正则表达式,时间,函数,...。怎样才能实现完全复制。
var d = Symbol();
var e = Symbol();
var ss = {
a: 1,
};
var date = new Date();
var divs = document.createElement("div");
divs.setAttribute("a", "3");
date.setFullYear(2025);
function Box() { }
Box.prototype.a = 10;
Box.prototype.play = function () {
console.log("aaa");
};
var obj = {
a: 1,
b: 2,
c: [1, 2, 3],
zz: new Set([1, 2, ss]),
yy: new Map(),
[d]: "aaa",
z: divs,
d: {
e: date,
f: /a/g,
g: function (s) {
console.log(s);
},
h: {},
},
ttt: Box,
};
obj.d.g.ssss = 10;
obj.eee = new Uint8Array([97, 98, 99]);
Object.defineProperties(obj.d.h, {
i: {
value: 10,
},
j: {
configurable: true,
writable: true,
value: [1, 2, 3, 4],
},
k: {
writable: true,
value: {
l: {},
m: "abcde",
n: true,
o: [1, 2, 3],
},
},
[e]: {
value: ["a", "b", "c", "e"],
},
});
obj.z.style.width = "50px";
obj.z.style.height = "50px";
obj.z.style.backgroundColor = "red";
Object.defineProperties(obj.d.h.k.l, {
p: {
value: function (a, b) {
console.log("p");
},
},
q: {
value: {
r: {
a: 1,
},
j: {
b: 2,
},
},
},
});
var a_1 = {
a: 1,
};
var a_2 = {
b: 2,
};
obj.yy.set("name", "xietian");
obj.yy.set(a_1, a_2);
Object.defineProperty(obj, "www", {
set: function (_v) {
this.a = _v;
},
get: function () {
return this.a;
},
});
封装实现深复制(背):
// obj:源对象,protoBool:,target:复制的对象
function deepClone(obj, protoBool = false, target) {
switch (true) {
//源对象是一个元素节点
case obj instanceof Node:
// 把源对象的元素节点赋值给复制对象。
target = obj.cloneNode(true);
break;
// 源对象是一个utfArray等类型
case obj.buffer instanceof ArrayBuffer:
// 源对象是一个时间对象
case obj instanceof Date:
// 源对象是一个集合
case obj instanceof Set:
// 源对象是一个映射
case obj instanceof Map:
// 源对象是一个正则
case obj instanceof RegExp:
// 创建一个和源对象相同的数据类型赋值给复制对象
target = new obj.constructor(obj);
break;
// 源对象是一个函数
case typeof obj === "function":
var str = obj.toString().replace(/\n/g, "");
var arr = str.match(/.*?\((.*?)\)\s*\{(.*)\}/);
target = new Function(arr[1], arr[2]);
break;
// 默认是对象类型
default:
// obj.constructor():能得到对象是什么类型的,new就是创建一个和源对象同类型的对象
if (!protoBool) target = new obj.constructor();
else target = {}
}
// 获取到源对象可枚举和不可枚举的任意字符类型的属性,返回一个源对象的属性数组
var names = Reflect.ownKeys(obj);
// 遍历
for (var i = 0; i < names.length; i++) {
// getOwnPropertyDescriptor(对象,属性):获取对象属性的描述对象desc
var desc = Object.getOwnPropertyDescriptor(obj, names[i]);
// 源对象属性的desc描述对象的value值存在并且该value值是对象类型或者该value值是函数
if (desc.value && typeof desc.value === "object" || typeof desc.value === "function") {
// 当前属性
if (names[i] === "constructor") {
Object.defineProperty(target, names[i], { value: desc.value })
continue;
}
// 定义复制对象的属性,target:复制对象, names[i]:源对象的属性
Object.defineProperty(target, names[i], {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
value: names[i] === "prototype" ? deepClone(desc.value, true) : deepClone(desc.value),
});
} else {
//非递归
// Object.defineProperty(对象, "属性名", 属性描述对象) :定义单个属性
// target:复制的对象,names[i]:当前属性,desc:源对象的描述对象
Object.defineProperty(target, names[i], desc);
}
}
return target;
}
var target = deepClone(obj);
// 改变源对象的属性,与复制对象做比较,查看是否会改变复制对象属性的值。
// 没有改变证明复制成功。
obj.yy.set("name", "zhangsan")
obj.d.h.k.l.q.j.b = 1000;
obj.ttt.prototype.a = 100;
console.log(target, obj);
源对象的属性:
复制对象的属性:
封装实现深复制修改版(背):
Object.prototype.deepClone = function (protoBool = false, target) {
switch (true) {
case this instanceof Node:
target = this.cloneNode(true);
break;
case this.buffer instanceof ArrayBuffer:
case this instanceof Date:
case this instanceof Set:
case this instanceof Map:
case this instanceof RegExp:
target = new this.constructor(this);
break;
case typeof this === "function":
var str = this.toString().replace(/\n/g, "");
var arr = str.match(/.*?\((.*?)\)\s*\{(.*)\}/);
target = new Function(arr[1], arr[2]);
break;
default:
if (!protoBool) target = new this.constructor();
else target = {};
}
var names = Reflect.ownKeys(this);
for (var i = 0; i < names.length; i++) {
var desc = Object.getOwnPropertyDescriptor(this, names[i]);
if ((desc.value && typeof desc.value === "object") || typeof desc.value === "function") {
if (names[i] === "constructor") {
Object.defineProperty(target, names[i], { value: desc.value });
continue;
}
Object.defineProperty(target, names[i], {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
value:
names[i] === "prototype"
? desc.value.deepClone(true)
: desc.value.deepClone(),
});
} else {
Object.defineProperty(target, names[i], desc);
}
}
return target;
};
var target = obj.deepClone()
console.log(target);
15. 柯里化(currying)
柯里化:简单来说就是把一个多参数的函数转化为单参数函数的方法。可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数。因为这样的特性,也被称为部分计算函数。柯里化,是一个逐步接收参数的过程。
1. 怎么实现调用该函数不管传入几个参数都能执行该函数并且返回正确答案?
function getSum() {}
getSum(1, 2, 3);
getSum(4, 5, 6);
getSum(7, 8);
2. 实现方案一:普通方法
Array.from(arrayLikearr,mapFn(element),thisArg):从迭代器或类数组对象创建一个新的浅拷贝的数组实例。
参数详解:
- arrayLikearr:想要转换成数组的类数组或可迭代对象。
- mapFn(element,index):每个将要添加到数组中的值首先会传递给该函数,然后将
mapFn
的返回值增加到数组中。element:数组当前正在处理的元素。index:数组当前正在处理的元素的索引。- thisArg:执行
mapFn
时用作this
的值。
var sum = 0;
function getSum() {
if (arguments.length > 0) {
sum = Array.from(arguments).reduce((v, t) => v + t, sum);
} else {
return sum;
}
}
getSum(1, 2, 3);
getSum(4, 5, 6);
getSum(7, 8);
var sum = getSum();
console.log(sum);
3. 实现方案二:闭包
function currying() {
var sum = 0;
return function (...arg) {
if (arg.length > 0) {
sum += arg.reduce((v, t) => v + t);
} else {
return sum;
}
}
}
var getSum = currying();
getSum(1, 2, 3);
getSum(4, 5, 6);
getSum(7, 8);
var sum = getSum();
console.log(sum);
4. 实现方案三:桥接模式(背)
function currying(fn) {
var arr = [];
return function fn2(...arg) {
if (arg.length > 0) {
arr.push(...arg);
return fn2
} else {
var value = fn(...arr);
arr.length = 0;
return value;
}
};
}
var getSum = currying(function (...arg) {
return arg.reduce((v, t) => v + t);
});
getSum(1, 2, 3);
getSum(4, 5, 6);
getSum(7, 8);
var sum = getSum();
console.log(sum);
5. 实现方法四: 封装柯里化(背)
Function.prototype.currying = function () {
var arr = []
var fn = this;
return function fn1(...arg) {
if (arg.length > 0) {
arr.push(...arg);
return fn1;
} else {
var value = fn(...arr);
arr.length = 0;
return value;
}
}
}
function getSum(...arg) {
return arg.reduce((v, t) => v + t);
}
var getS = getSum.currying();
getS(1, 2, 3);
getS(4, 5, 6);
getS(7, 8);
var s = getS();
console.log(s);
16. 反柯里化
反柯里化:就是将柯里化函数转换为接收多个参数的普通函数。
案例:元素反柯里化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<script>
// 反柯里化
// 给某个函数增加unCurrying属性
Function.prototype.unCurrying = function () {
// this指的是[].slice
var self = this;
return function () {
// Function.prototype.call 改变this指向self,
// Function.prototype.call.apply([].slice,[divs,0,3])
// call 、apply 立即执行函数,并且将函数内的this指向传入的第一个参数
return Function.prototype.call.apply(self, arguments);
};
};
// 测试
var divs = document.querySelectorAll("div");
var slice = [].slice.unCurrying();
var div1 = slice(divs, 0, 3);
console.log(div1);
</script>
</body>
</html>
16. 方法重构
- call 、apply 立即执行函数,并且将函数内的this指向传入的第一个参数。
- bind 给函数里面的this绑定为传入的第一个参数,但是不立即执行,什么时候调用,this就会执行这个参数。
1.案例一:重构数组slice方法
// 数组添加了一个slice_1属性,start:开始索引,end:结束索引
Array.prototype.slice_1 = function (start, end) {
// this是谁调用slice_1,this就是谁,这里数组
if (!start) start = 0;
if (!end) end = this.length;
// 输入的start参数的值小于0
if (start < 0) start = start + this.length > this.length ? this.length : start + this.length;
// 输入的end参数的值小于0
if (end < 0) end = end + this.length > this.length ? this.length : end + this.length;
// 如果传入的start参数的值不是数值类型,使用~~变成数值类型,如果是小数则取整
start = ~~start;
end = ~~end;
var arr = []
// 遍历
for (var i = start; i < end; i++) {
if (i in this) arr.push(this[i])
else arr.length++;
}
return arr;
}
// 测试
var arr = [1, 2, , 3, 4, 5];
arr.slice_1(1.2, 3.4)
var arr1 = arr.slice(1.2, 3.4)
console.log(arr1);
2. 案例二:函数call方法重构(背)
// 这里的thisArg是obj,...arg是1,3
Function.prototype.call_1 = function (thisArg, ...arg) {
// 谁执行call_1 this就是谁 ,这时的this是fn函数
if (thisArg == undefined) {
return this(...arg)//相当于fn(a, b);
}
// name = fn.name
var name = this.name;
// 给对象的原型添加一个属性为name=fn函数
Object.prototype[name] = this;
// value = obj.fn(1,3),这里obj调用了fn函数,this发生了改变指向obj
var value = thisArg[name](...arg);
// 删除对象的原型上添加的name属性
delete Object.prototype[name];
return value;
}
//测试:
var obj = { a: 2 }
function fn(a, b) {
console.log(this, "aaa", a, b);
}
fn.call_1(obj, 1, 3);
3. 案例三:函数apply方法重构
Function.prototype.apply_1 = function (thisArg, list) {
// if(thisArg===undefined || thisArg===null){
// 谁执行call_1 this就是谁
if (thisArg == undefined) {
return this(...list)
}
var name = this.name;
Object.prototype[name] = this;
var value = thisArg[name](...list);
delete Object.prototype[name];
return value;
}
4. 案例四:函数bind方法重构(背)
// bind重构 闭包
Function.prototype.bind_1 = function (thisArg, ...arg) {
var fn = this;
return function (...arg1) {
return fn.apply(thisArg, [...arg, ...arg1])
}
}
// 测试
var obj = { a: 1, b: 2 }
function fn(a, b, c) {
console.log(this, a, b, c);
}
var f = fn.bind_1(obj, 1, 2)
f(3);