对象 : 无序属性合集 其属性可以包含基本值 对象或函数
1.理解对象:
1.数据属性 : 有四个特征 [[Configurable]],[[Enumerable]],[[Writable]],[[value]], 修改单个属性使用Object.defineProperty() 参数: 属性所在对象,属性名字和描述符对象(只能是4个特征一个或多个)
var obj = {
name: 'lmx',
age: 12,
description: 'ojbk'
};
// configurable 默认true 改为false 不能通过delete 删除属性 也不能改为访问器属性
// configurable一旦改为false [[configurable]]与[[enumerable]]就不可以更改 否则会报错
Object.defineProperty(obj, 'name', {
configurable: false
});
delete obj.name; //无效
console.log(obj.name); //还会打印lmx
/* study.html:43 Uncaught TypeError: Cannot redefine property: name
at Function.defineProperty ()
at study.html:43 */
Object.defineProperty(obj, 'name', {
// enumerable: false, //会报错
// configurable: true //会报错
writable:false //不会报错
});
//writable 默认true 改为false 无法直接改变属性的值,但是可以通过改变[[value]]来改变
Object.defineProperty(obj, 'age', {
writable: false
});
obj.age = 18;
console.log(obj.age); //12
Object.defineProperty(obj, 'age', {
value: 22
});
console.log(obj.age); //22
//enumerable 默认为true 改为false 不能被for in 枚举
Object.defineProperty(obj, 'description', {
enumerable: false
});
for (const key in obj) {
console.log(key); // name age 没有description
}
//value 存放属性的值 默认为undefined 更改value可以改变属性的值
Object.defineProperty(obj, 'description', {
value: 'ppap'
});
console.log(obj.description); // ppap
2.访问器属性 : 访问器属性不能直接定义,必须使用Object.defineProperty() 定义,有四个特征 [[Configurable]],[[Enumerable]],[[Set]],[[Get]]
var obj = {
name: 'lmx',
age: 12,
description: 'ojbk',
_ppap: 'lala'
};
console.log(obj.name);
//访问器属性 不可以直接定义 但是这里我让其直接与name属性同名 并没有问题
// name属性就变成了访问器属性
Object.defineProperty(obj, 'name', {
get: function() {
return this.description;
},
set: function(newValue) {
this.description = newValue;
}
});
obj.name = '你好明天';
console.log(obj.name + '-------------' + obj.description); //你好明天-------------你好明天
//访问器属性 包含一对函数 get() set() 并不是必须的, 再读取的时候调用get() 在写入的时候调用set() 默认都是undefined
//如果缺少 get(), 变成只写, 缺少set(), 变成只读
Object.defineProperty(obj, 'ppap', {
get() {
return this._ppap;
},
set(newValue) {
this._ppap = newValue;
}
});
obj.ppap = '风往哪吹';
console.log(obj.ppap + '------------' + obj._ppap); //风往哪吹------------风往哪吹
var obj = {
name: 'lmx',
age: 12,
description: 'ojbk',
_ppap: 'lala'
};
//规定多个属性 规则与Object.defineProperty() 基本相同
Object.defineProperties(obj, {
name: {
configurable: false
},
age: {
writable: false,
value: 100
},
ppap: {
get() {
this._ppap;
}
}
});
var obj = {
name: 'lmx',
age: 12,
description: 'ojbk',
_ppap: 'lala'
};
//规定多个属性 规则与Object.defineProperty() 基本相同
Object.defineProperties(obj, {
name: {
configurable: false
},
age: {
writable: false,
value: 100
},
ppap: {
get() {
this._ppap;
}
}
});
//获取访问器属性 描述符
var ppapDescripter = Object.getOwnPropertyDescriptor(obj, 'ppap');
console.log(ppapDescripter);
//获取数据属性 描述符
var nameDescripter = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(nameDescripter);
var obj = {
name: 'lmx',
age: 12,
description: 'ojbk',
_ppap: 'lala'
};
//数据属性
Object.defineProperty(obj, 'name', {});
var descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log('数据属性--------------');
console.log(descriptor);
//访问属性
Object.defineProperty(obj, 'ppap', {
get() {
return this._ppap;
},
set(newValue) {
this._ppap = newValue;
}
});
var descriptor = Object.getOwnPropertyDescriptor(obj, 'ppap');
console.log('\n访问属性');
console.log(descriptor);
2.创建对象:
function SuperClass(name, age) {
var obj = {
name: name,
age: age
};
return obj;
}
var xiaoMing = SuperClass('xiaoming', 18);
var xiaoHong = SuperClass('xiaohong', 18);
//都包含相同属性名
console.log(xiaoMing); //{name: "xiaoming", age: 18}
console.log(xiaoHong); //{name: "xiaohong", age: 18}
构造函数模式 : 创建自定义的构造函数,从而定义自定义对象的属性和方法, 构造函数: 方法名首字母大写, 不会手动创建对象,没有返回值,将属性方法赋值给this,
1.new : 通过构造函数创建对象,需要使用new关键字, 执行new时会有4个步骤,下面是模拟代码
function SuperClass(name, age) {
//创建一个新对象
var obj = {};
//执行构造函数 实质执行SuperClass 模式改用SubClass
var result = SubClass.apply(obj, arguments);
//将obj的proto属性指向构造函数的prototype
//对象的属性查找实质就是通过_proto_一层一层往上查找_proto_,由自己的_proto_直到null为止
obj._proto_ = SuperClass.prototype;
//判断result的类型 如果是引用类型就赋值给obj引用否则返回obj
if (result instanceof Object) {
obj = result;
}
return obj;
}
function SubClass(name, age) {
this.name = name;
this.age = age;
}
2.构造函数的问题: 创建实例时每个方法都需要重新创建一遍,当然也可使用全局函数,但是对于我们的需求而言不合理
function SubClass(name, age) {
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(name);
}
}
person1 = new SubClass('lmx');
person2 = new SubClass('xml');
console.log(person1.sayHello == person2.sayHello);//false 也就是说 实例方法引用的并不是相同指针
//当然 我们可以使用指针的形式, 但是这并不单单属于实例的方法,而是全局方法,全部都可以调用这是不合理的
function Person(name){
this.name = name;
this.sayHi = sayHi;
}
function sayHi(){
console.log(this.name);
}
var person3 = new Person('lmx');
var person4 = new Person('xml');
console.log(person3.sayHi == person4.sayHi);//true
sayHi();
function SubClass() {};
SubClass.prototype.name = 'lmx';
SubClass.prototype.sayHello = function(){
console.log(this.name);
}
person1 = new SubClass();
person2 = new SubClass();
person1.sayHello();//lmx
person2.sayHello();//lmx
console.log(person1.sayHello == person2.sayHello);//true
1.理解原型对象:
当我们创建一个函数,就会为函数创建一个prototype属性,这个属性指向原型对象,每一个原型对象会默认获取一个constructor属性,这个属性指向prototype所在的函数, Person.prototype.curstructor = Person, 而每一个实例都有一个 指针叫[[prototype]](脚本无法访问) ,但是某些浏览器(chrome,ff)支持访问实现了一个_proto_属性, 这个属性指向的是构造函数的prototype, 所以实例与构造函数之间的联系与构造函数无关,只与构造函数的prototype 有关.
isPrototypeOf() 用来检测对象间是否存在依存关系,存在true 否则false
function SubClass() {};
SubClass.prototype.name = 'lmx';
SubClass.prototype.sayHello = function(){
console.log(this.name);
}
person1 = new SubClass();
person2 = new SubClass();
console.log(SubClass.prototype.isPrototypeOf(person1));//true
Object.getPrototypeOf() 会取得 这个对象得原型
function SubClass() {}
SubClass.prototype.name = 'lmx';
SubClass.prototype.sayHello = function() {
console.log(this.name);
};
person1 = new SubClass();
console.log(Object.getPrototypeOf(person1));
代码搜索实例得属性时,会先在实例本身查找,然后才会到原型中查找,这意味着我们只能通过对象实例获取原型得属性,却不能修改原型得属性,因而不必担心实例属性与原型属性重名得问题,js只会先读取实例得属性.
function SubClass() {}
SubClass.prototype.name = 'lmx';
SubClass.prototype.sayHello = function() {
console.log(this.name);
};
person1 = new SubClass();
person2 = new SubClass();
person1.name = 'lalalalla';
console.log(person1.name); // lalalalla
console.log(person2.name); //lmx
hasOwnProperty() : 检测属性是存在于实例(true) 还是 原型(false), 返回true或者false
function SubClass() {}
SubClass.prototype.name = 'lmx';
SubClass.prototype.sayHello = function() {
console.log(this.name);
};
person1 = new SubClass();
person2 = new SubClass();
person1.name = 'lalalalla';
console.log(person1.hasOwnProperty('name')); //true
console.log(person2.hasOwnProperty('name')); //false
2.原型与in操作符: in 判断对象是否有该属性(包含原型), for in (循环遍历,包括原型)可枚举得属性, Object.keys() 枚举实例对象可枚举的属性,Object.getOwnPropertyNames() 枚举实例对象所有属性
function SubClass() {}
SubClass.prototype.name = 'lmx';
SubClass.prototype.age = 18;
SubClass.prototype.sayHello = function() {
console.log(this.name);
};
person1 = new SubClass();
person1.name = 'lalalalla';
Object.defineProperty(person1,'name',{
enumerable:false
})
for (const key in person1) {
console.log(key); // age sayHello
}
console.log('name' in person1);//true
//Object.keys() 只得到可枚举得实例对象属性(不枚举原型)
console.log(Object.keys(person1));//[]
//Object.getOwnPropertyNames 得到实例对象本身所有的属性 无论是否可以枚举
console.log(Object.getOwnPropertyNames(person1));//["name"]
3.更简单的原型语法: 我们可以使用字面量写法重写函数的原型,这样可以减少不必要的输入,但是这样等于给函数的原型以字面量的形式创建了一个新对象,这也导致了Person.prototype.constructor 指向了Object 而不是Person了,当然我们也可重新给原型的constructor赋值Person,但是这样会导致constructor变得可枚举, 可能还需要Object.defineProperty()来重新修正
function Person() {}
Person.prototype = {
name: 'lmx',
age: 18
};
var xiaoHong = new Person();
for (const key in xiaoHong) {
console.log(key);//name age
}
console.log(xiaoHong.constructor);//ƒ Object() { [native code] }
function Person() {}
Person.prototype = {
constructor: Person,
name: 'lmx',
age: 18
};
var xiaoHong = new Person();
for (const key in xiaoHong) {
console.log(key); // constructor name age
}
console.log(xiaoHong.constructor); //ƒ Person() {}
function Person() {}
Person.prototype = {
constructor: Person,
name: 'lmx',
age: 18
};
//更改constructor 枚举修饰符
Object.defineProperty(Person.prototype,'constructor',{
enumerable:false
})
var xiaoHong = new Person();
for (const key in xiaoHong) {
console.log(key); // name age
}
console.log(xiaoHong.constructor); //ƒ Person() {}
4.原型的动态性: js中实例与原型之间是松耦合性,只有在原型中查找值得时候才算一次搜索,因此那怕我们在实例创建后再在原型中添加属性或者方法,实例对象也可以拿到, 但是 用字面量方式重写原型不可以, 由于先创建实例,然后再重写原型, 实例对象的[[prototype]]指向原型是Person.prototype 而用字面量方式 Person.prototype的指针变了,所以实例对象就无法拿到新的指针了
//直接再原型上添加属性
function Person() {}
var person1 = new Person();
Person.prototype.name = 'lmx';
console.log(person1.name);//lmx
//字面量形式更改 原型
function Student(){}
var xiaoHong = new Student();
Student.prototype = {
name:'xiaoHong'
}
console.log(xiaoHong.name);//undefined
5.原生对象的原型: 所有原生的引用类型,都是用原生模式创建的,我们可以给原生对象的原型添加方法(不推荐,可能会跟原生方法冲突)
6.原型对象的问题: 原型共享方法非常合适,但如果是引用类型,那么他的实例也会共享,实例一般要有属于自己的属性,使用共享的方法,所以原型很少被单独使用
//person1 与 person2 共享一个数组
function Person() {}
var person1 = new Person();
Person.prototype.sons = ['lmx', 'nihao', 'mingtian'];
var person1 = new Person();
var person2 = new Person();
person1.sons.push('haha');
console.log(person1.sons);//(4) ["lmx", "nihao", "mingtian", "haha"]
person2.sons.push('home');
console.log(person2.sons);//(5) ["lmx", "nihao", "mingtian", "haha", "home"]
//组合使用构造函数模式和原型模式
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log(this.age);
}
var person1 = new Person('lmx',18);
动态原型模式:前文中,原型与构造函数是分开的,其实可以把原型模式 写在构造函数里面,然后自己的判断是否使用原型(不能使用字面量方式更改prototype)
//动态原型模式
function Person(name, age) {
this.name = name;
this.age = age;
if (this.name !== 'lmx') {
Person.prototype.sayHi = function() {
console.log(this.age);
};
}
}
var person1 = new Person('lmx', 18);
person1.sayHi(); //not a function
寄生构造函数模式: 在构造函数中主动创建对象(自定义对象,或原生),并返回新对象, 目的, 在不改变原对象的构造函数情况下,又拥有自定义的方法(原生对象 Array Object等)
//寄生构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
if (this.name !== 'lmx') {
Person.prototype.sayHi = function() {
console.log(this.age);
};
}
}
//创建一个新的构造函数,在原构造函数的基础上更改
function customPerson(name,age){
var obj = new Person(name,age);
obj.sayHellow = function(){
console.log(this.name);
}
return obj;
}
var xiaoLiZi = new customPerson('xiaoli',27);
xiaoLiZi.sayHellow();
//实例与customPerson无关
console.log(customPerson.prototype.isPrototypeOf(xiaoLiZi))//false
console.log(Person.prototype.isPrototypeOf(xiaoLiZi))//true
//稳妥构造函数模式
function Person(name, age) {
var o = new Object();
var sonName = name;
o.saySonName = function() {
console.log(sonName);
};
return o;
}
var person1 = Person('lmx', 18);
person1.saySonName();
console.log(person1.sonName);//undefined
3.继承: js只有实现继承(没有接口继承), js实现继承主要是通过原型链实现的.
原型链继承,就是通过重写prototype来实现的,与字面量重写原型需要注意的点相似, 实例xiaoMing的原型的contructor指向的并不是默认的SubClass 而是其父类 SuperClass构造函数
//父类
function SuperClass(name, age) {
this.name = name || 'lmx';
SuperClass.prototype.sayHello = function() {
console.log(this.name);
};
}
//子类
function subClass() {
this.age = 18
}
//让子类的prototype = 父类实例
subClass.prototype = new SuperClass();
//子类实例
var xiaoMing = new subClass();
xiaoMing.sayHello();
console.log(Object.getPrototypeOf(xiaoMing).constructor);//SuperClass
console.log(xiaoMing instanceof subClass) //true
console.log(xiaoMing instanceof SuperClass) //true
console.log(xiaoMing instanceof Object) //true
1.别忘记默认原型: 引用类型都是继承自Object, 构造函数的原型中也存在[[prototype]],它指向了 Object.prototype.
2.确定原型和实例的关系: instanceOf 构造函数.prototype.isPrototypeOf() 前文讲过,不复述
3.谨慎的定义方法: 子类需要重写父类的方法时,指定要放在将代码放在替换原型语句之后,否则原型被重写我们实际调的还是父类的方法.
//父类
function SuperClass(name, age) {
this.name = name || 'lmx';
SuperClass.prototype.sayHello = function() {
console.log(this.name);
};
}
// //再重写前重写构造函数的方法
// subClass.prototype.sayHello = function(){
// console.log('hahah');
// }
//子类
function subClass() {
this.age = 18;
}
//让子类的prototype = 父类实例
subClass.prototype = new SuperClass();
subClass.prototype.sayHello = function() {
console.log('hahah');
};
//子类实例
var xiaoMing = new subClass();
xiaoMing.sayHello(); //代码在重写实例之前还是lmx 代码在重写之后hahah
4.原型链的问题:
问题1: 使用原型链继承,如果属性值为引用类型(数组,对象),那么会被所有实例共享
问题2: 没有办法在不影响所有实例的情况下,给构造函数传参
为了解决原型链引用类型与传参的问题,可以使用借用构造函数,实质就是在构造函数中使用call或者apply方式调用父类,保证是子类调用的方法,将其属性复制了一份,同时子类也可以自己定义想定义的属性,
//父类
function SuperClass(name, age) {
this.name = name || 'lmx';
}
//子类
function subClass() {
SuperClass.apply(this, arguments);
this.age = 18;
}
//让子类的prototype = 父类实例
subClass.prototype = new SuperClass();
//子类实例
var xiaoMing = new subClass('woani');
console.log(xiaoMing.name);//woani
将借用构造函数,与原型链相结合, 使用原型链中共享的方法同时每个实例还有自己的属性,实例之间互不影响
//父类
function SuperClass(name, age) {
this.name = name || 'lmx';
this.sons = ['xx','oo']
}
SuperClass.prototype.sayHellow = function(){
console.log('fff团万岁')
}
//子类
function subClass() {
SuperClass.apply(this, arguments);
this.age = 18;
}
//让子类的prototype = 父类实例
subClass.prototype = new SuperClass();
//子类实例
var xiaoMing = new subClass('woani');
var xiaoLi = new subClass('xx00');
xiaoMing.sons.push('风往哪吹');
xiaoLi.sons.push('明天');
console.log( xiaoMing.sons); //3) ["xx", "oo", "风往哪吹"]
console.log( xiaoLi.sons) //(3) ["xx", "oo", "明天"]
原型式继承的目的就是 基于已有对象, 在不自定义构造函数的前提下,创建新对象, 实质就是 创建一个临时构造函数,让他的原型等于目标对象, 在返回这个临时构造函数的实例, 这样既不用创建构造函数,还可以使用父类属性, 但并未解决属性是应用类型的问题
//原型式继承
var person = {
name: '溟',
age: 25
};
function copyObj(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var xiaoMing = copyObj(person);
console.log(xiaoMing.name);//溟
es5 标准化了原型式继承 使用 Object.create() 函数, 参数1 目标对象 参数2 已操作符的形式来修改属性 , 会覆盖父类同名属性.
//原型式继承
var person = {
name: '溟',
age: 25
};
var xiaoHomg = Object.create(person, {
name: {
enumerable: false,
value: '明天'
}
});
console.log(xiaoHomg.name + '--------' + xiaoHomg.age);// 明天--------25
寄生式继承与原生继承相似, 创建一个封装继承的函数,然后再函数内部增强对象(添加方法或属性) 最后返回这个对象
//寄生式继承
var person = {
name: '溟',
age: 25
};
function copyObj(obj) {
function F() {}
F.prototype = obj;
return new F();
}
function createAnother(obj){
var other = copyObj(obj);
other.sayHello = function(){
console.log('nihao');
}
return other
}
var xiaoMing = createAnother(person);
console.log(xiaoMing.name); //溟
与组合式继承相似, 寄生组合式继承同样也是通过借用构造函数的方式继承属性,利用原型链继承方法, 但是我们会发现在组合式继承中,调用了两次父类型的构造函数,一次在子类借用时,一次在重写子类原型的时候,寄生组合式继承,把组合式继承与寄生继承相结合,在原型继承中继承的是父类的prototype既只继承原型,而不是构造函数的实例,然后在寄生式中更改子类的原型,这样就实现了只调用了一次父类的构造函数.
正常的继承思路:
new SubClass ==> SubClass.prototype ==> SuperClass.prototype ==> Object.prototype
这也是为什么在寄生组合式继承中, 让临时的构造函数的prototype 等于 SuperClass.prototype 然后让Subclass.prototype = new 临时构造函数(), 如果直接修改 SubClass.prototype = SuperClass.prototyoe 那么原型链的顺序就会出错 变成了
new SubClass ==> SuperClass.prototype ==> Object.prototype
//寄生组合式继承
//父类
function SuperClass(name) {
this.name = name || 'lmx';
SuperClass.prototype.sayHi = function() {
console.log(this.name);
};
}
//子类
function SubClass(name){
SuperClass.apply(this,arguments);
this.age = 18
}
//孙子类
function GrandSonClass(name){
SubClass.apply(this,arguments);
this.description = '孙子儿';
}
//原生继承
function copy(proto) {
//使临时构造函数的原型 = 父类的原型
function F() {}
F.prototype = proto;
return new F();
}
//寄生继承
function inhert(sub, supreme) {
var other = copy(supreme.prototype);
//修正子类的构造函数
sub.prototype.constructor = sub;
//让子类的原型等于 临时构造函数的实例
sub.prototype = other;
}
inhert(SubClass,SuperClass)
inhert(GrandSonClass,SubClass)
var xiaoMing = new GrandSonClass('小明');
console.log(xiaoMing.name);// 小明
console.log(xiaoMing.age); // 18
console.log(xiaoMing.description); //孙子
错误的寄生组合式继承思路
//错误思路 虽然也可以进行继承 但是他们其实共用了一个原型
//与原型链继承顺序不符合
//父类
function SuperClass(name) {
this.name = name || 'lmx';
this.ah = 'ah';
SuperClass.prototype.sayHi = function() {
console.log(this.name);
};
}
//子类
function SubClass(name){
SuperClass.apply(this,arguments);
this.age = 18
}
SubClass.prototype = SuperClass.prototype;
var person = new SubClass('小明');
person.sayHi();
console.log(person.ah);
console.log(Object.getPrototypeOf(person) == SubClass.prototype) //true
console.log(Object.getPrototypeOf(person) == SuperClass.prototype) //true
console.log(person instanceof SuperClass); // true
console.log(person instanceof SubClass); // true
console.log(Object.getPrototypeOf(person).constructor);//SuperClass(name)