最近在复习基础知识,这次整理的内容是JS对象,原型,原型链,继承~
本次知识的复习是基于《JavaScript高级程序设计》加上自己的理解,如果有什么问题,望不吝赐教。
对象
什么是对象: 无序属性的集合,其属性可以包含基本值,对象或者函数。 在js中就是 key-value 的键值对
对象的属性类型
- 数据属性
- 访问器属性
数据属性
包含一个数据值的位置,可以读取和写入。
包含4个特性[Configurable, Enumerable, Value, Writable]
除Value
外,默认为true。这里表示的是 对象本身的属性
如果使用 Object.defineProperty(obj, va, des)
, 定义新属性或修改原有的属性, 那么这里的 des
,是个对象,包含对于{configurable: false || true, enumerable: false || true, value: undefined, writable: false || true}
的设置, 除value
外,默认为 false
。指的是 设置对象属性时
使用
var obj = { a: 20 };
// 未设置属性, 默认均为 false
Object.defineProperty(obj, 'b', { value: 25 })
// 这是设置 属性 后的
Object.defineProperty(obj, 'c', {
configurable: true, // 允许修改或删除
writable: true, // 允许重写
value: 30,
enumerable: true, // 允许可枚举
})
console.log('学习对象 1', obj); // {a: 20, c: 30, b: 25}
// b在c的后面,且是置灰状态,因为b不可枚举。
console.log(Object.keys(obj)); // ['a', 'c']
访问器属性
不包含数据值,是getter和setter函数
包含4个特性[Configurable, Enumerable, Get, Set]
,Get, Set
默认值为 undefined
,,Configurable, Enumerable
默认为true
。
只能使用 Object.defineProperty(obj, va, des)
来定义属性, {configurable: false || true, enumerable: false || true, get: undefined, set: undefined}
默认为false / undefined
使用
var obj1 = {};
var bValue;
Object.defineProperty(obj1,"newKey",{
get:function (){ return bValue },
set:function (value){ bValue = value },
configurable: true, // 允许修改或删除
enumerable: true, // 允许可枚举
});
console.log('学习对象2', obj1);
console.log(obj1.newKey);
console.log(Object.values(obj1));
总的来说,如果一个描述符不具有 value、writable、get 和 set
中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value
或 writable
和 get
或set
键,则会产生一个异常。
为对象一次定义多个属性
使用方法:
Object.defineProperties(a, b) // a: 要添加或修改其属性的对象 b: 要修改成什么样,与a对应,可以写成 {c:, e, g, s,...}
使用
var obj2 = {};
var vv;
Object.defineProperties(obj2, {
value1: {
value: '设置多个值',
// configurable: true,
// writable: true,
// enumerable: true,
},
newNumber: {
get: function(){
return vv
},
enumerable: true,
set: function(v) {
vv = v
},
configurable: true,
}
})
console.log('学习对象3', obj2);
还有两个方法,获取属性的特性值
Object.getOwnPropertyDescriptor(obj, prop)
返回指定对象上一个自有属性对应的属性描述符。自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性
Object.getOwnPropertyDescriptors(obj)
用来获取一个对象的所有自身属性的描述符
console.log('学习对象4', Object.getOwnPropertyDescriptor(obj2, 'value1')); // 会拿到对应属性的所有特性值,如没有设置,就是默认值
console.log('学习对象5', Object.getOwnPropertyDescriptors(obj2)); // 拿到所有属性的所有特性值,如没有设置,就是默认值
创建对象
以下几种方式: 1、直接创建 2、工厂模式 3、构造函数模式 4、原型模式 5、组合式使用构造函数和原型
直接创建
缺点: 代码量太多,不利于复用
var personObj = {
name: 'deidei',
jobNum: '007',
eat: function(v) {
return v;
}
}
// new Object({...})
console.log('学习对象直接创建', personObj, personObj.eat('meat'));
工厂模式
使用函数来封装特定接口来创建对象
function createPerson (name, jobNum){
var o = new Object();
o.name = name;
o.jobNum = jobNum;
o.eat = function(v) {
return v
}
return o;
}
console.log('学习对象工厂模式', createPerson('dei', '001'), createPerson('dei', '001').eat('rice'))
特点: 需要显式创建,需要return
优点: 解决了创建多个相似对象的问题
缺点: 不能识别对象的类型
构造函数模式
本身也是函数,在使用中可以new,也可以当作普通函数使用
function CreateStaff (name, jobNum){
this.name = name;
this.jobNum = jobNum;
this.eat = function (v){
return v
}
}
var staff2 = new CreateStaff('2', '002');
var staff3 = new CreateStaff('3', '003');
console.log('学习对象构造函数', staff2, staff2.eat('chicken'));
console.log('学习对象构造函数', staff3, staff3.eat('vegatable'));
// staff2 ,staff3 是 CreateStaff 的 实例,都有一个 constructor 的属性,指向 CreateStaff
console.log(staff2.constructor == CreateStaff, staff2.constructor == staff3.constructor); //true
特点:没有显式创建,不需要return,使用 this对象
优点: 将实例标识为特定的类型
缺点: + 每次new
一个对象,方法需要在构造函数上重新创建一遍,相当于 每次 都要new Function( return v )
。
+ 这样每次创建的实例都是包含不同的Function
实例,会导致不同的作用域链和标识符解析。但是没有必要,因为这两个不同Function
完成同样的任务。
=> 引出 原型模式
第一次解决: 将方法放在 全局作用域,在构造函数中直接调用全局的函数 =》 不可取, 封装性太弱,
第二次解决: 原型模式,原因:原型对象包含可以由特定类型的所有实例共享的属性和方法。
原型模式
给原型对象上添加属性和方法,可以让对象实例共享
function CreateStaffNew() {
}
CreateStaffNew.prototype.name = '4';
CreateStaffNew.prototype.jobNum = '004';
CreateStaffNew.prototype.eat = function(v) { return v }
var staff4 = new CreateStaffNew();
var staff5 = new CreateStaffNew();
console.log('学习对象原型模式', staff4.eat('furit'), staff5.eat('fish'), staff4.eat == staff5.eat); // furit fish true
// 属性和方法都是共享的。
原型对象 :任何函数,都有一个propotype属性,这个属性所指向的就是这个函数的原型对象。
// 原型详解:
// prototype: 任何函数,如构造器,普通函数等 都有 原型;这个原型也一定有个 constructor 属性,指向构造函数本身。
console.log('学习对象原型模式 原型', CreateStaffNew.prototype.constructor == CreateStaffNew) // true
// __proto__ : 隐式原型,所有js对象都有,指向创造这个对象的 构造器 的 原型,即构造器函数也有,指向Function的原型,再指向 Object 的原型
console.log('学习对象原型模式 原型', staff4, staff4.__proto__ == CreateStaffNew.prototype); // true
console.log('学习对象原型模式 原型', CreateStaffNew.prototype.isPrototypeOf(staff4)) // true 判断实例对象与构造器之间 是否有 原型指向 的关系
console.log('学习对象原型模式 原型', Object.getPrototypeOf(staff5) == CreateStaffNew.prototype) // true
console.log('学习对象原型模式 原型', Object.getPrototypeOf(staff4).name) // 4
区分这个属性或方法来自原型链还是实例:
hasOwnProperty()
不会查找原型链 in
会查找原型链
console.log('学习对象原型模式 hasOwnProperty fasle', staff4.hasOwnProperty('name')); //false
staff4.name = '测试hasOwnProperty'
console.log('学习对象原型模式 hasOwnProperty true', staff4.hasOwnProperty('name')); // true
确定属性或方法是原型的还是实例的:
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object); // 实例上不存在 && 原型上存在 =》 在原型上
}
重写原型对象
function ReCreateStaff(){
}
// 先创建实例
var re = new ReCreateStaff();
// 重写原型对象,切断了现有原型与任何之前已经存在的实例之间的关系,re引用的仍然是最初的原型
ReCreateStaff.prototype = {
name: '9',
jobNum: '009',
sayName: function() {
return this.name
}
};
var re1 = new ReCreateStaff();
// re.sayName(); // error re.sayName is not a function
console.log(re1.sayName()) // 9
缺点: 属性不可复用,都被写到原型对象上了,也就是说只能用于一个对象的。这也就是为何不单独使用原型模式
组合式创建
使用构造函数定义不需要共享的属性,使用原型模式定义需要共享的属性和方法。
function CreateStaffComcat(name, jobNum){
this.name = name;
this.jobNum = jobNum;
}
CreateStaffComcat.prototype.eat = function(){
return this.name
}
var staff6 = new CreateStaffComcat('6', '006');
console.log('学习对象组合模式', staff6, staff6.eat())
继承
原型链
使用原型,让一个引用类型继承另一个引用类型的属性和方法。
子构造函数的原型指向了父构造函数的实例对象,因此子构造函数的实例对象可以通过原型链找到父构造函数的原型方法和类属性。
function CreateStaffPro(name) {
this.name = name;
}
CreateStaffPro.prototype.say = function() { return this.name }
function Engineer() {
}
Engineer.prototype = new CreateStaffPro(); // 子构造函数的原型 指向 父构造函数的实例
var e1 = new Engineer('bbb111'); // 无法传递
// 子构造函数也就拥有 父构造函数的属性和方法; 所有实例对象都可以共享父构造函数的原型方法
console.log('学习继承原型链', e1.name, e1.say()); // undefined, undefined
// 但是并不是 Engineer 的属性和方法,而是在执行的时候,需要再往上找一层,找到父构造函数。这样实现不太好,如果找不到,就要一直向上找到原型链的末端。
// 所有函数的默认原型都是 Object()的实例
// 使用 instanceof ; isPrototypeOf() 检测 原型和实例 之间的关系
console.log('学习继承原型链 instanceof', e1 instanceof Engineer, e1 instanceof CreateStaffPro); // true, true
console.log('学习继承原型链 isPrototypeOf', Engineer.prototype.isPrototypeOf(e1), CreateStaffPro.prototype.isPrototypeOf(e1)); // true, true
缺点:
- 引用类型值的原型属性会被所有实例共享,如果继承,实例属性就会变成现在的原型属性。
- 创建子构造函数的实例时,无法向父构造函数中传递参数
=》 很少单独使用原型链 来实现继承
借用构造函数
其实就是在子类构造函数中,借调 父类的构造函数, 使用 call()/apply() 方法。
function CreateStaffGou(name) {
this.name = name;
this.hi = function() { return 'hi' }
}
CreateStaffGou.prototype.say = function() {return this.name}
function EngineerGou(name) {
CreateStaffGou.call(this, name); // 只能拿到构造函数内部的属性和方法,但拿不到 原型上定义的共享方法
}
var e2 = new EngineerGou('ccc111');
console.log('学习继承构造函数',e2.name, e2.hi()); // ccc111 hi
// console.log(e2.say()) // error
优点:相比原型模式,有很大的一个优势,就是可以,传递参数
缺点:
- 实现了 属性的传递,但是 方法都定义在子类的构造函数里,无法复用
- 在父构造函数中定义的方法,在子构造函数中,不可见
=》 很少单独使用 构造函数 来实现继承
组合继承
借用构造函数继承 不需要共享的属性,使用 原型链来继承 需要共享的属性和方法。
function CreateStaffComcatExtend(name){
this.name = name;
}
CreateStaffComcatExtend.prototype.sayName = function() { return this.name }
function EngineerExtend(name, jobNum) {
// 继承属性
CreateStaffComcatExtend.call(this, name)
this.jobNum = jobNum;
}
// 继承方法
EngineerExtend.prototype = new CreateStaffComcatExtend();
EngineerExtend.prototype.sayJobNum = function() { return this.jobNum }
var e3 = new EngineerExtend('10', '010');
console.log('学习继承组合式',e3.name, e3.sayName(), e3.sayJobNum()) // 10 10 010
缺点:调用两次父构造函数,一次是原型链继承(继承方法),一次是在借调构造函数继承(继承属性) =》 产生两个 name 属性
原型式继承
寄生式继承
寄生组合继承
解决 组合式继承的问题
实现:借调构造函数继承属性(与组合继承的构造函数一致),使用原型链的混成形式继承方法(以父类的原型为原型的构造函数实例化出来的对象作为子类的原型(Object.create做的事情),完美避开了不必要的父类构造函数里的东西。)
子类.原型 = new Object(父类.原型) 子类.原型.constructor = 子类
只调用一次 父构造函数
function CreateStaffComcatJiSheng(name) {
this.name = name;
}
CreateStaffComcatJiSheng.prototype.sayName = function() { return this.name }
function EngineerExtendJiSheng(name, jobNum) {
// 继承属性 不变化
CreateStaffComcatJiSheng.call(this, name)
this.jobNum = jobNum;
}
console.log('看看Object(f.prototype)是个什么东西', CreateStaffComcatJiSheng.prototype, Object.create(CreateStaffComcatJiSheng.prototype))
// 替换 z.prototype = new f()
function inheritPrototype(f, z){
// var prop = Object(f.prototype); //创建对象 使用父构造函数的副本
z.prototype = Object(f.prototype); //指定对象
// prop.constructor = z; //增强对象
}
inheritPrototype(CreateStaffComcatJiSheng, EngineerExtendJiSheng);
EngineerExtendJiSheng.prototype.sayJobNum = function() {return this.jobNum};
var e4 = new EngineerExtendJiSheng('11', '011');
console.log('学习继承寄生组合式', e4.name, e4.sayName(), e4.sayJobNum()); // 11 11 011
关注我获取更多前端资源和经验分享
感谢大佬们阅读,希望大家头发浓密,睡眠良好,情绪稳定,早日实现财富自由~