对象的概述
什么是对象?
对象是一个具体的事物,包含一系列的属性,这些属性是无序的,每一属性都有一个字符串key和对应的value
在js语言中,除了基本数据类型剩下的就是对象了
对象原型上的属性
Object.prototype上的属性
- obj.constructor ; 指向创建该对象的构造函数
- obj.hasOwnProperty(propertyName); 返回一个boolean类型的值
- obj.isPrototypeOf(obj2); 判断obj是不是obj2的原型
- obj.propertyIsEnumerable(propertyName)
- obj.toLocaleString();
- obj.toString();
- obj.valueOf();
属性操作
读写对象属性 : 读取对象obj.z; 写对象obj.z=3; 循环读写对象属性obj[string]
属性异常
删除属性 : delete obj.z
检测属性 : hasOwnProperty 或 in
枚举属性 : obj.propertyIsEnumerable(propertyName), 一个boolean值
设置枚举属性 : Object.defineProperty(obj, "price", {enumerable : false, value : 1000});为obj设置一个不可枚举的属性price
- 判断对象是否拥有某个属性
obj.hasOwnProperty(propertyName)
该方法不判断原型链上的属性,只判断自身拥有的属性,自身拥有即为true,否则为false
判断对象或对象的原型链上是否拥有某一属性可以使用in,如
console.log("name" in obj)
,如果在原型链上能找到name属性即返回true,反之为false-
判断是否可以使用delete关键字删除对象的属性,可以查看对象的自身属性描述中的configurable是否为true,当描述对象中的configurable值为true时才可以删除对象的属性。
delete Object.prototype; //false var descriptor = Object.getOwnPropertyDescriptor(Object,"prototype"); console.log(descriptor); /* configurable: false enumerable: false value: {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} writable: false __proto__: Object */ descriptor.configurable; //false,代表不允许删除或删除失败 //定义的全局变量也不能通过delet删除 var globalVal = 1; delete globalVal; //false
// Object.defineProperty(obj, propertyName, {}); // Object.defineProperty(为某个要添加属性的对象, 要添加的属性名, 对将被修改或定义的属性的描述); var obj = {} Object.defineProperty(obj, 'li', {value: '厉害了...'});
对象的描述符
通过Object.defineProperty(obj, propertyName, descriptor);
如下的属性都是描述符对象(descriptor)的key
- writable 可写的
- enumerable
- configurable
- value
- get
- set
对象属性实例:
var obj = {x:1};
console.log(obj.y); //undefined
var yz = obj.y.z; //TypeError:Cannot read property "z" of undefined
obj.y.z = 2; //TypeError:Cannot set property "z" of undefined
var yz = obj && obj.y && obj.y.z; //当obj、obj.y、obj.y.z都存在时,给yz赋值
//---------------------------------------------------------------------------------
var cat = new Object();
cat.legs = 4;
cat.name = "kitty";
console.log("legs" in cat); //true
console.log("abc" in cat); //false
console.log("toString" in cat);//true
console.log(cat.hasOwnProperty("legs")); //true
console.log(cat.hasOwnProperty("toString")); //false
console.log(cat.propertyIsEnumerable("legs")); //true
console.log(cat.propertyIsEnumerable("toString")); //false
Object.defineProperty(cat,"price",{enumerable:false, value:1000});
console.log("price是否可枚举: "+cat.propertyIsEnumerable("price")); //false
//---------------------------------------------------------------------------------
//过滤原型链上的属性
var o = {x:1,y:2,z:3};
var obj = Object.create(o);
obj.str = "我是自身属性";
for(var key in obj) {
//console.log(key); // 会打印原型链o上的属性
}
for(var key1 in obj) {
if (obj.hasOwnProperty(key1)){ // 过滤o的属性
console.log(key1);
}
}
创建对象的方式(js中)
1.字面量的方式创建对象
2.使用new关键字创建对象(new构造器)
3.通过原型继承创建对象(Object.create(obj.prototype))
-
字面量的方式创建对象
var obj = { name : "zhangsan", age : 18, sayHi : function () { console.log("你好啊!我是:" + this.name) } }
-
使用new关键字创建对象
var obj1 = new Object(); obj1.name = "123"; // 动态的给对象添加属性,js是一种动态的语言 console.log(obj1.name); // 123
使用构造函数创建对象
function Cat (name, color) { this.name = name; this.color = color; this.run = function() { console.log("跑得快"); } } var cat = new Cat("coco","white"); console.log(cat.name); // coco console.log(cat.color); // white
-
通过原型继承创建对象
/** * 使用对象原型创建新的实例 * @param obj 是被创建出来的对象的原型对象 * @returns 返回对象 */ function createObj(obj){ if (obj == null) throw TypeError; if (Object.create) { return Object.create(obj); } if (typeof obj !== "object" && typeof obj !== "function") throw TypeError; function F(){}; F.prototype = obj; return new F(); } // 创建一个空对象 var nullObj = createObje(Object.prototype); // 和直接量{}一样
对象的方法
1.公共方法
在原型上定义公共方法要比在构造函数中定义更节省硬件的损耗
// 构造函数
function User(name,age) {
this.name = name;
this.age = age;
this.study = function(course){
console.log('正在学习'+course);
}
}
// 给构造函数的原型对象添加方法,
// 这些方法在每个由User构造器实例化出来的对象都可以访问
// 称为公共方法
User.prototype.getName = function () {
return this.name;
}
User.prototype.getAge = function () {
return this.age;
}
var u = new User('张三',20);
console.log(u.getName());
console.log(u.getAge());
2.私有方法
对象的私有方法只能内部调用
function Classroom(students, teacher) {
this.students = students;
this.teacher = teacher;
// 私有方法,返回学生的数量
_studentsCount = function () {
console.log('学生总数:' + students.length);
}
// 对象内部调用
_studentsCount();
}
var classOne = new Classroom(['小张','小米'],'Mr.Li');
console.log(classOne.teacher);
// classOne._studentsCount(); // 错误,因为不是对象的公共属性
3.特权方法
特权方法可以通过自身的公有方法访问到自身的私有成员。
// 特权方法:获取用户的出生年份
function User(name, age) {
// 私有变量
var year = (new Date()).getFullYear() - this.age;
this.name = name;
this.age = age;
// 创建一个特权方法,能够访问私有变量year,并保证自身是公有可访问的
this.getBornYear = function () {
console.log(year);
}
}
var user = new User('大熊', 1989);
user.getBornYear();
4.静态方法
静态方法通过构造函数名就可以直接调用
/**
* 动态生成方法(get/set)
* @param properties 是一个对象
* @constructor
*/
function User(properties) {
for (var item in properties) {
(function (currentObj) {
var p = item;
// get 获取某个属性
currentObj['get'+p] = function () {
return properties[p];
}
// set 设置某个属性
currentObj['set'+p] = function (val) {
properties[p] = val;
}
})(this); // 将当前对象传递到闭包中
}
}
// 创建一个新的用户实例,并把有两个属性的对象作为参数传递进入
// user对象的属性是私有的,只能通过get/set获取
var user = new User({
name: '张三',
age: 18,
});
// console.log(user.name); // 错误的undefined , 会给对象添加属性
console.log(user.getname());
// 给User对象添加静态方法,可以做任意的事情
User.staticMethod = function () {
console.log('我是静态方法')
};
// 调用静态方法
User.staticMethod();
对象的访问方式
- 对象的属性访问,可以通过点语法访问符合
标识符
命名规则的属性。如:object.property- 对象的
key
访问,可以访问不符合标识符
的命名规则的属性。如:object['prefix-name']标识符命名规则:只能包含字母、美元符合、数字和下划线,并且不能以数字开头
Object.defineProperty(obj, "x", { value : 1 }); 此时,enumerable默认为false
obj.x = 1;如果使用直接赋值的方式创建对象的属性,则这个属性的enumerable为true
使用构造函数创建对象的升级
- 构造函数里面只定义属性
- 方法放在构造函数的原型上定义。这样做的目的可以减少每次创建对象时需要的内存空间,只需开辟用于存放属性的空间就可以了,至于方法可以通过对象的内部原型
.__proto__
到构造函数的原型上调用需要的方法。
// 未升级的写法
function Cat (name, color) {
this.name = name;
this.color = color;
this.run = function() {
console.log("跑得快");
}
}
// 第一次升级的写法
function Cat (name, color) {
this.name = name;
this.color = color;
}
Cat.prototype = {
// 在构造函数的原型上定义run方法,这样所有通过构造函数Cat创建出来的对象都可以使用run方法
run = function() {
console.log("跑得快");
}
}
// 第二次升级
function Cat (options) {
this.name = name;
this.color = color;
}
Cat.prototype = {
run = function() {
console.log("跑得快");
}
}
var cat = new Cat({
name: "susu",
color: "black"
});
// 第三次升级
function Cat (options) {
this._init(options);
}
Cat.prototype = {
run = function() {
console.log("跑得快");
},
_init : function (options) {
this.name = options.name || "";
this.color = options.color || "";
}
}
使用原型的注意事项
1.使用对象访问属性的时候,如果在本身内找不到就会去原型中查找;但是使用点语法进行属性赋值的时候,并不会去原型中进行查找;使用点语法赋值的时候,如果对象中不存在该属性,就会给该对象新增该属性,而不会去修改原型中的属性(实例只使用原型不修改原型,但原型是可以被修改的)
2.如果在原型中的属性是引用类型的属性,那么所有的对象共享该属性,并且一个对象修改了该引用类型属性中的成员,其他对象也都会受影响 (原型中的引用类型属性被修改,其他引用该属性的对象的值也跟着被改变,因为引用类型的赋值是一个地址)
3.一般情况下不会将属性放到原型对象中,一般情况下原型中只会放需要共享的方法或常量
// 构造函数
function Person(){
}
// 字面量对象
var x = {
brand:"laosilaisi",
type:"huanying"
};
// 为Person构造函数的原型添加属性,该属性是一个引用类型的
Person.prototype.car = x;
var p = new Person();
console.log(p.car.brand); // laosilaisi
// 替换Person原型上的属性car的引用(此时的car指向另一个对象,该对象已经不是x了)
Person.prototype.car = {
brand:"BYD"
};
var p1 =new Person();
console.log(p1.car.brand); // BYD
console.log(p.car.brand); // BYD
p.car = {
// 为p对象添加一个car属性,此时的p只会使用自身的car属性,不会再去原型对象上找car属性了
};
console.log(p.car.brand); // 在自身找到car属性 undefied
// 结论:
// 1.通过实例是不能修改原型上的成员的,但是可以访问原型上的成员。
// 2.通过实例的构造函数的prototype属性可以修改原型。
// 3.原型上的属性是引用类型时,只要被修改,引用该属性的其他变量的值也发生变化。
对象与原型及原型链之间的关系
构造器:
Foo
(每一个构造器都有prototype属性)构造器原型:
Foo.prototype
(构造器原型也是对象,所以也有proto属性)对象:
obj
(所有的对象都有proto属性)
当定义一个函数function Foo(){}
时,系统会默认的为该函数Foo
添加一个属性prototype
,当通过函数Foo
使用new关键字创建对象时obj
时,obj可以通过对象自身的属性proto
指向该对象的构造器的原型对象Foo.prototype
function Foo () {
// 系统会默认为该函数添加prototype属性
}
Foo.prototype.z = 3; // 原型上的属性
var obj = new Foo(); // 创建实例obj
//obj可以通过自身的proto属性指向构造器(Foo)的原型对象(Foo.prototype),从而可以访问到在原型链中的z
console.log(obj.z); //3
obj.z = 5;
console.log(obj.z); //5
delete obj.z; //true
console.log(obj.z); //3
delete Foo.prototype.z; //true
console.log(obj.z); //undefined
实例截图
1
2
深拷贝/浅拷贝
浅拷贝:只拷贝当前对象中的所有属性(即只复制一层对象的属性)
深拷贝:而深复制则递归复制了对象所有层级的属性,包括属性中的引用类型
/**
* 浅拷贝
* @param src
* @returns {{}}
*/
function shallowCopy(obj) {
var newObj = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
newObj[prop] = obj[prop];
}
}
return newObj;
}
/**
* 深拷贝
* @param obj
*/
var deepCopy = function(obj) {
var str; // 通过序列化与反序列化也可以创建对象,所有定义了该变量
var newObj = obj.constructor === Object ? {} : [];
if (typeof obj !== 'object'){
return;
}else if(window.JSON){
str = JSON.stringify(obj); // 序列化对象
newObj = JSON.parse(str); // 还原
}else{
for (var key in obj) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
var obj = { a:1, arr: [2,3] };
// 浅复制会导致 obj.arr 和 shallowObj.arr 指向同一块内存地址,
// 任何一方被修改,另一方也被改变
var shallowObj = shallowCopy(obj);
shallowObj.arr.push('浅拷贝对象会改变原对象的引用类型属性的值');
console.log(obj);
console.log(shallowObj);
var deepObj = deepCopy(obj);
deepObj.arr.push('深拷贝');
console.log(obj);
console.log(deepObj);