js中的对象(上)
基础知识
对象是包括属性与方法的数据类型,JS 中大部分类型都是对象如 String/Number/Math/RegExp/Date 等等。
传统的函数编程会有错中复杂的依赖很容易创造意大利式面条代码。
所以我们使用面向对象的编程思想,将方法封装到对象中,同时也方便取值。
OOP
- 对象是属性和方法的集合即封装
- 将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象
- 继承是通过代码复用减少冗余代码
- 根据不同形态的对象产生不同结果即多态
基本声明
使用字面量形式声明对象是最简单的方式
在对象中的函数称为方法,我们可以这样声明func: function(){},也可以这样简写func(){}
let obj = {
age: 18,
fucn(){
console.log(this.age);//不需要传值,直接使用this取值
}
};
obj.fucn();//18
其实字面量形式在系统内部也是使用构造函数 new Object创建的,后面会详细介绍构造函数。
let hd = {};
let houdunren = new Object();
console.log(hd, houdunren);
console.log(hd.constructor);
console.log(houdunren.constructor);
操作属性
使用点语法.获取,或者使用中括号[]获取。
let obj = {
age: 18,
'user name': 'xx'
};
console.log(obj.age);//18
console.log(obj['age']);//18
可以看出使用.操作属性更简洁,[]主要用于通过变量定义属性的场景
let obj = {
age: 18,
};
let dd = 'age';
console.log(obj.dd);//undefined
console.log(obj[dd]);//18
如果属性名不是合法变量名就必须使用扩号的形式了,使用点语法会报错。
let obj = {
'user name': 'xx',
'uu-dd': 123
};
console.log(obj['user name']);//xx
console.log(obj['uu-dd']);123
对象属性的添加删除
对象和方法的属性可以动态的添加或删除。
let obj = {
name: 'tt'
};
obj.age = 19;
console.log(obj.age);//19
obj['dd'] = '这是dd';
console.log(obj.dd);//这是dd
obj.func = function(){
console.log('this is a func');
}
obj.func();//this is a func
console.log(obj.name);//tt
delete obj.name;
console.log(obj.name);//undefined
函数的传参(引用/值?)
当给函数传递参数时,如果传递的是值类型,就是传值。如果传递的是引用类型,就是传递地址。
let n = 1;
let user= {};
function funcN(a){
a += 10;
console.log(a);
}
//传值,两个变量不同内存地址
funcN(n);//11
console.log(n);//1
function funcObj(obj){
obj.name = 'xiaoxiao';
console.log(obj);
}
//传址,两个变量同一内存地址
funcObj(user);//{ name: 'xiaoxiao' }
console.log(user);//{ name: 'xiaoxiao' }
展开语法...在对象中的使用
使用...可以展示对象的结构,下面是实现对象合并的示例
let obj = {
name: 'xx',
age: 20
}
newObj = {...obj};
console.log(newObj);//{ name: 'xx', age: 20 }
newObj1 = {...obj,love: 'js'};
console.log(newObj1);//{ name: 'xx', age: 20, love: 'js' }
我们可以用来进行函数参数合并
let obj = {
type: '弱类型',
name: 'js'
}
newObj = {...obj, type: '强类型', name: 'ts'};
console.log(newObj);//{ type: '强类型', name: 'ts' }
解构赋值
解构语法在对象中的使用
es6中,我们可以使用解构语法来对变量进行赋值
let obj = {
type: '弱类型',
name: 'js'
}
let {type: a, name: b} = obj;
console.log(a,b);//弱类型 js
let {type: type, name: name} = obj;
console.log(type, name);//弱类型 js
如果需要赋值的变量与对象的属性名一致,我们可以简写
let obj = {
type: '弱类型',
name: 'js'
}
let {type, name} = obj;
console.log(type,name);//弱类型 js
当然,如果函数的返回值是一个对象,我们也可以用解构赋值来赋值。因为func()同样是一个对象。
function func(){
return {
type: '弱类型',
name: 'js'
}
}
let {type, name} = func();
console.log(type,name);//弱类型 js
我们也可以用解构赋值来想函数传递参数,这通常应用于后端传过来的参数(是一个对象)。
function func({name,age}){
console.log(name,age);
}
func({name: 'xiaoxiao', age: 20});//xiaoxiao 20
当然,我们也不一定要将对象中的全部属性对外面的属性赋值。
console.log(Math);//Math对象
let {random} = Math;
console.log(random());//0.8685842409329827
严格模式
非严格模式可以不使用声明指令,严格模式下必须使用声明。所以建议使用 let 等声明。
下面的代码在非严格模式下是可以使用的,但在严格模式下不能进行。
let obj = {
name: 'xiao',
age: 20
};
({name,age} = obj);
console.log(name, age);//xiao 20
所以我们一般使用let/const声明语句声明
"use strict";
let obj = {
name: 'xiao',
age: 20
};
let {name,age} = obj;
console.log(name, age);//xiao 20
简洁定义
如果属性名与赋值的变量名相同可以更简洁
let obj = {
name: 'xiao',
age: 20
};
let {name,age} = obj;
console.log(name,age);//xiao 20
只赋值部分变量,在数组的解构赋值中,我们要取到中间的值需要用逗号隔开,而对象的解构赋值我们可以直接取。
let arr = [1,2,3];
let [,b] = arr;
console.log(b);//2
let obj = {
name: 'xiao',
age: 20
};
let {age} = obj;
console.log(age);//20
可以直接使用变量赋值对象属性
let a = '我是a', b = '我是b';
console.log({a,b});//{ a: '我是a', b: '我是b' }
多层对象的解构赋值操作
let obj = {
name: 'obj',
lesson: {
title: 'houdunren'
}
}
let {name, lesson:{title}} = obj;
console.log(name,title);//obj houdunren
解构的默认值配置
在数组的解构赋值中,我们可以这样设置默认值
let arr = [1,2];
let [a,b,c=3] = arr;
console.log(a,b,c);//1 2 3
同样的在对象中,我们也可以这样操作
let obj = {
name: 'xiao',
age: 20
}
let {age,name,like='js'} = obj;
console.log(name,age,like);//xiao 20 js
我们也可以在函数中这样使用
function theObj(obj = {}){
let {title='js',click=88,author='xj'} = obj;
console.log(title,click,author);
}
theObj();//js 88 xj
theObj({title:'ts',click:50});//ts 50 xj
函数参数
function func(demo,{title, detail:{click, author}}){
console.log(demo,title, click, author);
}
func('这是demo',{title: 'js', detail:{click: 88, author: 'xj'}});//这是demo js 88 xj
检测属性(涉及部分原型链知识)
hasOwnProperty检测对象自身是否包含指定的属性,不检测原型链上继承的属性。
let arr = [1,2];
console.log(arr.hasOwnProperty('length'));//true
console.log(arr.hasOwnProperty('concat'));//false
使用 in 可以在原型对象上检测
let arr = [1,2];
console.log('concat' in arr);//true
我们可以设置某个对象的原型对象
<body>
</body>
<script>
let obj = {
func(){
return 123;
}
};
let objSon = {};
Object.setPrototypeOf(objSon,obj);
console.log(objSon);//可以看到他的原型对象为obj
console.log(objSon.func());//123
console.log(objSon.hasOwnProperty('func'));//false
console.log('func' in objSon);//true
</script>
对此,可以用来进行一传递些对象参数的应用
<body>
</body>
<script>
let func = function(options){
if(!options.hasOwnProperty('url')) {
throw new Error('没有设置url');
}
}
func({user: 'kk'});//Uncaught Error: 没有设置url
</script>
assign
以往我们使用类似jQuery.extend 等方法设置属性,现在可以使用 Object.assign 静态方法
从一个或多个对象复制属性(浅拷贝)
// 使用Object.assign方法(浅拷贝)
let hd = { a: 1, b: 2 };
Object.assign(hd, { f: 1 }, { m: 9 });
console.log(hd);//{ a: 1, b: 2, f: 1, m: 9 }
JSON.stringify
JSON.stringify可将值转换为JSON字符串
<body>
</body>
<script>
let lesson = [
{
title: 'js',
category: '弱类型'
},{
title: 'ts',
category: '强类型'
},{
title: 'golang',
category: '强类型'
}
];
let res = lesson.reduce((obj,cur,index)=>{
obj[`${cur['category']}-${index+1}`] = cur;
return obj;
},{});
console.log(JSON.stringify(res,null,2));
</script>
遍历对象
获取内容
使用系统提供的 API 可以方便获取对象属性与值
let lesson = {
title: 'js',
click: 88
};
console.log(Object.values(lesson));//[ 'js', 88 ]
console.log(Object.keys(lesson));//[ 'title', 'click' ]
console.log(Object.entries(lesson));//[ [ 'title', 'js' ], [ 'click', 88 ] ]
for/in
使用for/in遍历对象属性
let lesson = {
title: 'js',
click: 88
};
for(let keys in lesson){
console.log(keys,lesson[keys]);
}
// title js
// click 88
for/of
for/of用于遍历迭代对象,不能直接操作对象。但Object对象的keys/方法返回的是迭代对象。
let lesson = {
title: 'js',
click: 88
};
for(const keys of Object.keys(lesson)){
console.log(keys);
}
// title
// click
获取所有对象属性
let lesson = {
title: 'js',
click: 88
};
for(const values of Object.values(lesson)){
console.log(values);
}
// js
// 88
同时获取属性名与值
let lesson = {
title: 'js',
click: 88
};
for(const [keys,values] of Object.entries(lesson)){
console.log(keys,values);
}
// title js
// click 88
遍历添加元素 DOM 练习
<body>
</body>
<script>
let lessons = [
{ name: "js", click: 23 },
{ name: "ts", click: 12 }
];
let ul = document.createElement('ul');
for (const value of lessons) {
let li = document.createElement('li');
li.innerHTML = `课程${value.name} ${value.click}`;
ul.appendChild(li);
}
document.body.appendChild(ul);
</script>
对象拷贝
对象赋值时复制的内存地址,所以一个对象的改变直接影响另一个
let obj = {
name: '后盾人',
user: {
name: 'hdcms'
}
}
let a = obj;
let b = obj;
b.name = 'lisi';
console.log(a.name); //lisi
浅拷贝
使用for/in执行对象拷贝
let obj = {
name: 'xiaoxiao',
user: {
name: 'hdcms'
}
};
let newObj = {};
for (const key in obj) {
newObj[key] = obj[key];
}
obj.name = 'xj';
obj.user = {};
console.log(newObj);//{ name: 'xiaoxiao', user: { name: 'hdcms' } }
console.log(obj);//{ name: 'xj', user: {} }
Object.assign 函数可简单的实现浅拷贝,它是将两个对象的属性叠加后面对象属性会覆盖前面对象同名属性。
let obj = {
name: 'xiaoxiao',
user: {
name: 'hdcms'
}
};
let newObj = {};
Object.assign(newObj,obj)
obj.name = 'xj';
obj.user = {};
console.log(newObj);//{ name: 'xiaoxiao', user: { name: 'hdcms' } }
console.log(obj);//{ name: 'xj', user: {} }
使用展开语法也可以实现浅拷贝
let obj = {
name: 'xiaoxiao',
user: {
name: 'hdcms'
}
};
let newObj = {...obj};
obj.name = 'xj';
obj.user = {};
console.log(newObj);//{ name: 'xiaoxiao', user: { name: 'hdcms' } }
console.log(obj);//{ name: 'xj', user: {} }
深拷贝
浅拷贝不会将深层的数据复制,例如:
let obj = {
name: 'xiaoxiao',
user: {
name: 'hdcms'
}
};
let newObj = {...obj};
obj.name = 'xj';
obj.user.name = 'mmm';
console.log(newObj);//{ name: 'xiaoxiao', user: { name: 'mmm' } }
console.log(obj);//{ name: 'xj', user: { name: 'mmm' } }
我们可以使用函数来进行实现
let demoObj = {
name: 'xiaoxiao',
user: {
name: 'hdcms'
},
arr: []
};
let supCopy = function(object){
//因为数组与对象的typeof的值都为object,所以需要用instanceof来区分(不然数组也会变成对象)
let obj = object instanceof Array ? [] : {};
for (const [k,v] of Object.entries(object)) {
obj[k] = typeof v === 'object' ? supCopy(v) : v;//如果是引用类型,就再次调用函数,参数为值
}
return obj;
}
let newObj = supCopy(demoObj);
demoObj.arr.push('xj');
demoObj.user.name = 'ddwb';
console.log(newObj);//{ name: 'xiaoxiao', user: { name: 'hdcms' }, arr: [] }
console.log(demoObj);//{ name: 'xiaoxiao', user: { name: 'ddwb' }, arr: [ 'xj' ] }
使用函数创建对象
对象可以通过内置或自定义的构造函数创建。
工厂函数
在函数中返回对象的函数称为工厂函数,工厂函数有以下优点
- 减少重复创建相同类型对象的代码
- 修改工厂函数的方法影响所有同类对象
使用字面量创建对象需要复制属性与方法结构,使用工厂函数可以简化这个过程:
function createObj(name,age){
return {
name,
age,
showName(){
console.log(`名字为${this.name}`);
},
showAge(){
console.log(`${this.name}的年龄为${this.age}`);
}
};
}
createObj('kk',20).showName();//名字为kk
createObj('ddwb',22).showAge();//ddwb的年龄为22
构造函数
和工厂函数相似构造函数也用于创建对象,它的上下文为新的对象实例。
- 构造函数名每个单词首字母大写即
Pascal命名规范 this指当前创建的对象- 不需要返回
this系统会自动完成 - 需要使用
new关键词生成对象
function User(name){
this.name = name;
this.show = () => {
console.log(name);
}
}
let user = new User('kevin');
user.show();//kevin
在非严格模式下,this的指向为调用构造函数中方法的对象,我们在全局调用func,那么this指向的就是window对象。
<body>
</body>
<script>
function User(title){
this.title = title;
this.show = function() {
console.log(this.title);
console.log(this);
}
//不需要返回,系统会自动返回
// return this;
//如果设置返回值,那么返回值就是设置的那个
// retrun {}; 这时候返回值就是个空对象
}
let user = new User('kevin');
user.show();
//kevin
//传进User构造函数返回的对象
let func = user.show;
func();
//undefined
//window对象
</script>
严格模式
在严格模式下方法中的this值为 undefined,这是为了防止无意的修改 window 对象
<body>
</body>
<script>
'use strict';
function User(){
this.show = function() {
console.log(this);
}
//不需要返回,系统会自动返回
// return this;
//如果设置返回值,那么返回值就是设置的那个
// retrun {}; 这时候返回值就是个空对象
}
let user = new User();
user.show();
//传进User构造函数返回的对象 User {show: ƒ}
let func = user.show;
func();
//undefined
</script>
内置构造
JS 中大部分数据类型都是通过构造函数创建的。
const num = new Number(88);
console.log(num.valueOf());//88
const str = new String('kevin');
console.log(str.valueOf());//kevin
const boo = new Boolean(true);
console.log(boo.valueOf());//true
const date = new Date();
console.log(date.valueOf() * 1);//1657804424179
const regexp = new RegExp('\\d+');
console.log(regexp.test(99));//true
const obj = new Object();
obj.title = 'js';
console.log(obj);//{ title: 'js' }
字面量创建的对象,内部也是调用了Object构造函数
const obj = {};
console.log(obj.constructor);////ƒ Object() { [native code] }
对象函数
在JS中函数也是一个对象
function func(){};
console.log(func.toString());//function func(){}
console.log(func.length);//0
函数是由系统内置的 Function 构造函数创建的
function func() {}
console.log(func.constructor);//[Function: Function]
下面是使用内置构造函数创建的函数
const User = new Function(`name`,`
this.name = name;
this.show = ()=>{
return this.name;
};
`
);
const kevin = new User('kevin');
console.log(kevin.show());//kevin
抽象特性
将复杂功能隐藏在内部,只开放给外部少量方法,更改对象内部的复杂逻辑不会对外部调用造成影响即抽象。
问题分析
下例将对象属性封装到构造函数内部
function User(name,age){
this.name = name;
this.age = age;
this.show = ()=>{
let judge = this.age > 30 ? `中年` : `青年`;
console.log(`现在${this.name}是${judge}`);
}
}
let kevin = new User('kevin',20);
kevin.show();//现在kevin是青年
kevin.name = 'tom';
kevin.show();//现在tom是青年
抽象封装
上例中的方法和属性仍然可以在外部访问到,比如 info方法只是在内部使用,不需要被外部访问到这会破坏程序的内部逻辑。
下面使用闭包特性将对象进行抽象处理
function User(name,age){
let data = {name,age};
this.show = ()=>{
let judge = data.age > 30 ? `中年` : `青年`;
console.log(`现在${data.name}是${judge}`);
}
}
let kevin = new User('kevin',20);
kevin.show();//现在kevin是青年
kevin.name = 'tom';
kevin.show();//现在kevin是青年