「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
对象,类和面向对象编程
对象:对象由键值对组成的无序集合
对象的属性类型
1.数据属性
数据属性包含一个保存数据的位置。值会从这个位置读取,也会写入到这个位置。
数据属性有一下四个特性:
- [[Configurable]] : 表示属性是否可以通过delete删除并重定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认是true
- [[Enumberable]]:是否可以通过for-in循环返回,默认为true
- [[Writable ]] :表示属性的值是否可以被修改。默认为true
- [[Value] ]: 包含属性实际的值
可以通过Object.defineProperty( )
方法来修改属性的默认特性,接受三个参数:属性的对象,属性的名称,一个描述符对象
*注意:*当创建一个对象的新属性是,不设置该属性的特性时,默认都为false。
//通过Object.defineProperty() 方法修改了默认特性
let person = {
id: 1,
age: 12,
name: 'li',
hobby: 'run'
};
//注意:configurable设置为false后,不能修改configurable和Enumerable两个特性,不然会报错
Object.defineProperty(person, 'name', {
configurable: false,
value: 'wang1'
});
console.log(person.name);
delete person.name;
console.log(person.name);
Object.defineProperty(person, 'name', {
configurable: 'wang'
});
2.访问器属性
访问器属性不包含数据值。它包含一个获取函数和设置函数,但两个函数不是必要的。
使用场景:
- 隐藏某一个私有属性,不希望直接被外界使用。
- 如果我们希望截获某一个属性他访问和设置值得过程,也会使用访问器属性描述符
4个特性:
- [[Configurable]] : 表示属性是否可以通过delete删除并重定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认是true
- [[Enumberable]]:是否可以通过for-in循环返回,默认为true
- [[Set ]] : 设置函数,在写入属性时调用,默认值为undefined
- [[Get]] : 获取函数,在获取属性时调用,默认值为undefined
使用场景:设置一个属性值来改变其他属性的值
Object.defineProperty(person, 'name', {
get() {
return this._name;
},
set(newvalue) {
this._name = newvalue;
}
})
console.log(person._name); //li
person.name = 'wagn';
console.log(person._name); //wagn
属性操作
定义多个属性:Object.defineProperties(对象,描述符)
读取属性的特性: Object.getOwnPropertyDescriptor(对象, 要取得其描述符的属性名)
读取整个对象的属性的特性:Object.getOwnPropertyDescriptors( 对象)(es8新增)
let book = {};
//定义多个属性
Object.defineProperties(book, {
_year: {
value: 2017
},
edition: {
value: 1
},
year: {
get() {
return this._year
},
set(newValue) {
if (newValue > 2017) {
this._year = newValue;
this.edition += newValue - 2017
}
}
}
})
//读取属性
let descriptor = Object.getOwnPropertyDescriptor(book, '_year');
console.log(descriptor.value);
console.log(descriptor.configurable);
console.log(descriptor.get);
let descriptor1 = Object.getOwnPropertyDescriptor(book, 'year');
console.log(descriptor1.value);
console.log(descriptor1.enumerable);
console.log(descriptor1.get);
//读取整个对象的属性的特性
console.log(Object.getOwnPropertyDescriptors(book));
Object方法
- 禁止对象扩展新属性:preventExtensions()
- 密封对象,不允许配置和删除属性 :seal( )
- 冻结对象,不允许修改现有属性: freeze ()
创建多个对象的方式
1. new Object( ) 和{ }
缺点:创建同样的对象时,需要编写重复的代码;
2. 工厂函数
通过工厂函数创建多个对象,实质上就是通过一个函数来创建对象,并将对象作为返回值返回。
例如:通过creatPerson()工厂函数创建对象
function creatPerson(name, age, address) {
var p = new Object();
p.name = name;
p.age = age;
p.address = address;
return p;
}
var p1 = creatPerson('张三', 12, '北京市');
var p2 = creatPerson('王五', 23, '郑州市');
弊端:通过工厂函数创建出来的对象都是Object类型,获取不到对象最真实的类型。
3.构造函数
构造函数:js中的构造函数就是一个普通的函数,只是通过new 来调用时,可以当做构造函数来使用。
通过new调用函数执行如下操作:
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;;
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
例如:
function Person(name, age, address) {
this.name = name;
this.age = age;
this.address = address;
this.eating = function() {
console.log(this.name + '在吃东西');
}
}
var p1 = new Person('王五', 12, '郑州市');
var p2 = new Person('李四', 13, '北京市');
缺点:每次创建对象时都要创建一个eating方法,这样会浪费内存。
创建对象的内存状态:
对象原型
每一个对象内部都有一个[[prototype]]属性,这个特殊的属性可以指向另一个对象(隐式原型)
访问方式:1. __ proto __ ( 浏览器内置的方式,有兼容性问题)
- Object.getPrototypeOf( obj)
函数原型
所有的函数都有一个prototype属性,可以通过prototype来访问函数的原型。
function foo(){}
console.log(foo.prototype);
默认情况下原型上有construtor属性的,指向当前的函数对象(foo),并且是不可枚举的。
优化后的构造函数创建对象:
function Person(name, age, address) {
this.name = name;
this.age = age;
this.address = address;
}
//重写原型对象
Pobject = {
eating: function() {
console.log(this.name + '在吃东西');
},
runing: function() {
console.log(this.name + '在跑步');
}
}
//constructor不可枚举
Object.defineProperty(Pobject, 'constructor', {
enumerable: false, //不可枚举
value: Person
})
Person.prototype = Pobject;
var p1 = new Person('王五', 12, '郑州市');
var p2 = new Person('李四', 13, '北京市');
console.log(p1);
console.log(p2);
p1.eating();
p2.runing();
面向对象
三大特征:封装,继承,多态
原型链
每一函数都它的显示原型,对象有它的隐式原型。
函数原型:
function foo() {} 函数的原型链结构:
foo.prototype -> { }
{}.proto -> Object.prototype
Object.prototype.proto -> null
对象原型:
obj ={ }对象的原型链结构:
obj.proto -> Object.prototype
Object.prototype.proto -> null
注意 : Object是原型链的尽头 , Object对象的原型就是null
通过原型链继承
//父类
function Person() {
this.name = 'why'
}
Person.prototype.eating = function() {
console.log(this.name + '吃饭');
}
//子类
function student() {
this.sno = '123';
}
student.prototype = new Person();
student.prototype.studing = function() {
console.log(this.name + '学习');
}
var stu = new student();
console.log(stu);
弊端:
- 我们通过直接打印对象是看不到这个属性的;
- 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
- 不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化);
借用构造函数继承
//父类
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.eating = function() {
console.log(this.name + '吃饭');
}
//子类
function student(name, age, friends) {
Person.call(this, name, age, friends);
this.sno = '123';
}
student.prototype = new Person();
student.prototype.studing = function() {
console.log(this.name + '学习');
}
var stu = new student('王', 12, ['afe']);
var stu2 = new student('li1', 13, ['eee']);
弊端:
- Person构造函数会被调用两次
- stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要
对象的原型继承
var obj = {
name: '王'
}
//对象原型继承
//1.creat函数创建一个对象,该对象的原型指向obj
var newobj = Object.create(obj);
//2.创建一个对象,通过setPrototypeOf()函数将newobj的原型指向obj对象
function creatObj1() {
var newobj = {};
Object.setPrototypeOf(newobj, obj);
return newobj;
}
//3.通过构造函数Fun,将Fun的原型指向obj对象,因此new Fun()构造函数创建的对象的原型也是指向Obj对象的
function creatObj2() {
function Fun() {};
Fun.prototype = obj;
var newobj = new Fun();
return newobj;
}
寄生组合式继承
最优的继承方案:解决了借用构造函数继承产生多余属性的问题,并且不会调用两次父类
//寄生组合式继承
//对象原型继承
function creatObject(o) {
function Fun() {}
Fun.prototype = o;
return new Fun();
}
//修改类型
function inheritPrototype(SubType, SuperType) {
SubType.prototype = creatObject(SuperType.prototype);
Object.defineProperty(SubType.prototype, 'constructor', {
configurable: true,
enumerable: false,
writable: true,
value: SubType
})
}
//父类
function Person(name, age, friends) {
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function() {
console.log("running~");
};
Person.prototype.eating = function() {
console.log("eating~");
};
//子类
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
//创建一个对象,让这个对象的原型指向Person的原型对象,
//然后将这个的对象赋值给Student的原型
//避免了直接new Person()创建出来的对象会产生多余的属性
Student.prototype = creatObject(Person.prototype);
// inheritPrototype(Student, Person);
Student.prototype.studying = function() {
console.log("studying~");
};
//老师
function Teacher(name, age, friends, tno) {
Person.call(this, name, age, friends);
this.tno = tno;
}
Teacher.prototype = creatObject(Person.prototype);
// inheritPrototype(Teacher, Person);
Teacher.prototype.teaching = function() {
console.log('~教学');
}
var stu1 = new Student("王五", 16, ["老李"], 01, 99);
console.log(stu1);
stu1.studying();
stu1.running();
var stu2 = new Student("李四", 12, ["老王"], 02, 60);
console.log(stu2);
stu2.studying();
stu2.running();
var teacher1 = new Teacher('王老师', 45, ['李老师'], 003);
console.log(teacher1);
teacher1.teaching();
teacher1.running();
对象方法
hasOwnProperty: 对象是否有某一个属于自己的属性(不是在原型上的属性)
in :判断某个属性是否在某个对象或者对象的原型上
instanceof: 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
isPrototypeOf: 用于检测某个对象,是否出现在某个实例对象的原型链
原型继承关系
//foo的显示原型prototype指向自己的原型对象
//函数也是对象,也有隐式原型(__proto__),
//foo函数对象是由Function函数创建的,所以指向Function.prototype
function foo() {}
//fn的原型指向foo函数的原型
var fn = new foo();
console.log(fn.__proto__ === foo.prototype); //true
console.log(foo.__proto__ === Function.prototype); //true
//foo.prototype也是对象,所以原型指向Object.prototype
console.log(foo.prototype.__proto__ === Object.prototype); //true
//Function函数也是函数,有自己的prototype和__proto__
//因为Function是由自己创建的,所以prototype === __proto__
console.log(Function.prototype === Function.__proto__); //true
//Function.prototype是对象,所以指向Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); //true
//Object函数也是由Function函数创建的,所以__proto__指向Function.prototype
//Object函数也有自己的prototype
console.log(Object.__proto__ === Function.prototype); //true
var obj = {};
//obj对象是由Object()函数创建的,所以___proto__ 指向Object.prototype
console.log(obj.__proto__ === Object.prototype); //true
经典原理图: