JS中面向对象
一、对象
1.认识对象
在JavaScript中,对象(Object)是一种复合数据类型,它允许你存储键值对。对象的属性是连接到对象的变量,而函数或方法是属于对象的函数。
JavaScript中的对象类似于哈希表,其中键可以是字符串或符号,值可以是任意类型的值,包括其他对象。对象字面量是用花括号 {}
包围的,其中冒号 :
用于分隔键和值,逗号 ,
用于分隔不同的键值对。
<!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><script>var xiaoming = {name: '小明',age: 12,sex: '男',hobbies: ['足球', '编程'],'favorite-book': '舒克和贝塔'};console.log(xiaoming.name);console.log(xiaoming.age);console.log(xiaoming.sex);console.log(xiaoming.hobbies);console.log(xiaoming['favorite-book']);var key = 'sex';console.log(xiaoming[key]);console.log();Math.ceil();</script>
</body>
</html>
对象属性的修改
<!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><script>var obj = {a: 10,b: 20};// 对象属性的修改obj.b = 40;obj.b++;console.log(obj.b);// 对象属性的增加obj.c = 60;console.log(obj);// 对象属性的删除delete obj.a;console.log(obj);</script>
</body>
</html>
2.对象的方法
在JavaScript中,面向对象的方法通常是在构造函数或者类中定义的。
<!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><script>var xiaoming = {name: '小明',age: 12,sex: '男',sayHello: function () {console.log('你好我是小明,我12岁了,我是一个男生');},sleep: function () {console.log('小明开始睡觉zzzzz');}};var xiaohong = {name: '小红',age: 11,sex: '女',sayHello: function () {console.log('你好我是小红,我11岁了,我是一个女生');},sleep: function () {console.log('小红开始睡觉zzzzz');}};xiaoming.sayHello();xiaohong.sayHello();xiaohong.sleep();</script>
</body>
</html>
3.遍历对象
<!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><script>var obj = {a: 11,b: 22,c: 88};for (var k in obj) {console.log('对象obj的属性' + k + '的值是' + obj[k]);}</script>
</body></html>
4.对象的深浅克隆
对象是引用类型
在 JavaScript 中,对象是通过引用传递的,这意味着当我们将一个对象赋值给另一个变量时,我们实际上是在复制该对象的引用,而不是复制整个对象。因此,当我们改变通过引用访问的对象时,所有对该对象的引用都会看到这种变化。
<!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><script>// 例子1var obj1 = {a: 1,b: 2,c: 3};var obj2 = {a: 1,b: 2,c: 3};console.log(obj1 == obj2); // falseconsole.log(obj1 === obj2); // falseconsole.log({} == {}); // falseconsole.log({} === {}); // false// 例子2var obj3 = {a: 10};var obj4 = obj3;obj3.a ++;console.log(obj4); // {a: 11}</script>
</body>
</html>
在JavaScript中,对象的克隆可以通过浅克隆和深克隆来实现。以下是两种克隆方法的实现方式:
浅克隆
浅克隆只会复制对象的第一层属性,如果属性值是基本类型,则拷贝的是基本类型的值;如果属性值是引用类型,则拷贝的是内存地址,因此如果原始对象或克隆对象中的引用类型属性发生变化,另一个对象也会受到影响。
以下是实现浅克隆的一种简单方法:
function shallowClone(obj) {const cloneObj = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = obj[key];}}return cloneObj;
}const original = { a: 1, b: { c: 2 } };
const cloned = shallowClone(original);
console.log(cloned); // { a: 1, b: { c: 2 } }
cloned.b.c = 3;
console.log(original); // { a: 1, b: { c: 3 } } // 原始对象也被修改了
<script>var obj1 = {a: 1,b: 2,c: [44, 55, 66]};// 实现浅克隆var obj2 = {};for (var k in obj1) {// 每遍历一个k属性,就给obj2也添加一个同名的k属性// 值和obj1的k属性值相同obj2[k] = obj1[k];}// 为什么叫浅克隆呢?比如c属性的值是引用类型值,那么本质上obj1和obj2的c属性是内存中的同一个数组,并没有被克隆分开。obj1.c.push(77);console.log(obj2); // obj2的c属性这个数组也会被增加77数组console.log(obj1.c == obj2.c); // true,true就证明了数组是同一个对象</script>
深克隆
深克隆会复制对象的所有层级属性。对于每个引用类型的属性,都会创建一个新的对象或数组,从而不会相互影响。
以下是实现深克隆的一种方法,它考虑了数组和对象,但并不包含对函数、循环引用或特殊对象(如Date、RegExp等)的处理:
function deepClone(obj, hash = new WeakMap()) {if (obj === null) return null; // 处理null值if (obj instanceof Date) return new Date(obj); // 处理日期if (obj instanceof RegExp) return new RegExp(obj); // 处理正则if (typeof obj !== 'object') return obj; // 如果不是复杂数据类型,直接返回// 如果是对象或数组,先检查hash中是否克隆过,解决循环引用问题if (hash.has(obj)) {return hash.get(obj);}// 初始化返回结果,保证数组和对象的原型不丢失let cloneObj = Array.isArray(obj) ? [] : {};// 将对象或数组存入hash中hash.set(obj, cloneObj);// 遍历对象的keyfor (const key in obj) {if (obj.hasOwnProperty(key)) {// 递归克隆每个值cloneObj[key] = deepClone(obj[key], hash);}}// 返回克隆后的对象return cloneObj;
}const original = { a: 1, b: { c: 2, d: { e: 3 } } };
const cloned = deepClone(original);
console.log(cloned); // { a: 1, b: { c: 2, d: { e: 3 } } }
cloned.b.d.e = 4;
console.log(original); // { a: 1, b: { c: 2, d: { e: 3 } } } // 原始对象未被修改
需要注意的是,上述深克隆方法并不完美,它没有处理函数、循环引用、特殊对象类型等复杂情况。在实际应用中,你可能需要使用更完善的库,比如 lodash
的 _.cloneDeep
方法,来处理这些情况。
<!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><script>var obj1 = {a: 1,b: 2,c: [33, 44, {m: 55,n: 66,p: [77, 88]}]};// 深克隆function deepClone(o) {// 要判断o是对象还是数组if (Array.isArray(o)) {// 数组var result = [];for (var i = 0; i < o.length; i++) {result.push(deepClone(o[i]));}} else if (typeof o == 'object') {// 对象var result = {};for (var k in o) {result[k] = deepClone(o[k]);}} else {// 基本类型值var result = o;}return result;}var obj2 = deepClone(obj1);console.log(obj2);console.log(obj1.c == obj2.c); // falseobj1.c.push(99);console.log(obj2); // obj2不变的,因为没有“藕断丝连”的现象obj1.c[2].p.push(999);console.log(obj2); // obj2不变的,因为没有“藕断丝连”的现象</script>
</body></html>
二、认识函数上下文
这个this.a+this.b=? 不是3 而是不知道 为什么不知道呢 因为函数只有被调用的时候才知道上下文。如果是obj.fn()那么答案是3;
但如果是var fn =obj.fn;
fn();
还是输出NaN不知道;因为圆括号直接调用,函数中的this指代windows对象。
<script>var obj = {a: 1,b: 2,fn: function() {console.log(this.a + this.b);console.log(this === window);}};// var a = 4;// var b = 9;obj.fn();console.log('---------');var fn = obj.fn; //不加圆括号,代表提炼函数本身fn();</script>
函数的上下文由调用函数的方式决定
规则1:对象.方法();对象打点调用它的方法函数,则函数的上下文是这个打点的对象;
规则2:函数();圆括号直接调用函数,则函数的上下文是window对象
var fn=obj1.fn;这是把这个函数提取出来,和obj1中的属性值就没有关系了。fn()在调研这个fn函数。
规则3:数组[下标]();数组(类数组对象)枚举出进行调用,上下文是这人数组(类数组对象)
规则4:立即可执行函数,上下文是window对象
规则5:定时器、延时器调用函数,上下文是window对象
规则6:事件处理函数的上下文是绑定事件的DOM元素
Demo1:点击哪个盒子哪个盒子就变红
<!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: 200px;height: 200px;float: left;border: 1px solid #000;margin-right: 10px;}</style>
</head>
<body><div id="box1"></div><div id="box2"></div><div id="box3"></div><script>function setColorToRed() {this.style.backgroundColor = 'red';}var box1 = document.getElementById('box1');var box2 = document.getElementById('box2');var box3 = document.getElementById('box3');box1.onclick = setColorToRed;box2.onclick = setColorToRed;box3.onclick = setColorToRed;</script>
</body>
</html>
Demo2:点击哪个盒子哪个盒子2000ms后就变红
延时器中的this指代的是window对象,所以要备份自身。可以使用self、_this表示备份
<!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: 200px;height: 200px;float: left;border: 1px solid #000;margin-right: 10px;}</style>
</head>
<body><div id="box1"></div><div id="box2"></div><div id="box3"></div><script>function setColorToRed() {// 备份上下文var self = this;setTimeout(function() {self.style.backgroundColor = 'red';}, 2000);}var box1 = document.getElementById('box1');var box2 = document.getElementById('box2');var box3 = document.getElementById('box3');box1.onclick = setColorToRed;box2.onclick = setColorToRed;box3.onclick = setColorToRed;</script>
</body>
</html>
call和apply
call和apply能指定函数的上下文
<script>function sum() {alert(this.c + this.m + this.e);};var xiaoming = {c: 100,m: 90,e: 80,sum:sum};//小明是不能直接打点调用sum的,必须要在xiaoming对象里面添加sum方法。xiaoming.sum();</script>
<script>function sum() {alert(this.c + this.m + this.e );};var xiaoming = {c: 100,m: 90,e: 80};sum.call(xiaoming);sum.apply(xiaoming);</script>
call和apply的区别
<!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><script>function sum(b1, b2) {alert(this.c + this.m + this.e + b1 + b2);};var xiaoming = {c: 100,m: 90,e: 80};sum.call(xiaoming, 3, 5);sum.apply(xiaoming, [3, 5]);</script>
</body>
</html>
这道题会显示77