1,对象、类与面向对象编程
1,关于创建对象
// 设置对象可以有两种方式进行设置
一种是直接加属性,一种是构造器的方式
let persion = new Object();
persion.name = "lisi";
persion.age = 19;
// 通过构造器的方式
let persion = {
name : 'lisi',
age : 19
};
2,关于数据属性
其实内置属性包含很多,这些内置属性默认为true。
默认属性为两种:数据属性和访问器属性
关于数据属性:
[[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特 性都是 true,如前面的例子所示。
[[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true,如前面的例子所示。
[[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的 这个特性都是 true,如前面的例子所示。
[[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性 的默认值为 undefined。
// 默认的Configurable,Enumerable,Writable都为true
let persion = {
name : 'lisi',
age : 19
};
// 关于Configurable,Enumerable,Writable属性的设置
let persion = {
};
Object.defineProperty(persion,'name',{
Writable:false,
value : "wangwu"
});
// 删除也是一个道理,不允许被删除
Object.defineProperty(persion,'name',{
Configurable:false,
value : "wangwu"
});
console.log(persion.name); // wangwu
delete persion.name
console.log(persion.name); // wangwu
3,关于访问器属性
访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。
[[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性 都是 true。
[[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true。
[[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
[[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
其实也就是可以重构get和set方法。
let persion = {
age_ : 19,
name : 'zhangsan'
};
Object.defineProperty(persion,"age",{
get(){
return this.age_;
},
set(age){
if(age>18){
this.age_ = age+1;
this.name = "age+1";
}
}
});
persion.age = 17;
console.log(persion.age);
persion.age = 19;
console.log(persion.age);
4,属性特点
属性访问可以通过Object.defineProperty(),这种方式可以进行处理,如果想设置多个属性可以使用 Object.defineProperties()方法进行批量设置
let persion = {
age_ : 19,
name : 'zhangsan'
};
Object.defineProperties(persion,{
age_ :{
value : 18
},
name : {
value : "lisi"
},
age:{
get : function(){
return this.age_;
},
set : function(age){
if(age>18){
this.age_ = age+1;
this.name = "age+1";
}
}
}
})
persion.age = 19;
console.log(persion.name); // age+1
console.log(persion.age_); // 19
如果想查具体属性,Object.getOwnPropertyDescriptor();去进行查询
// 不是从构造器设置的,是没有configurable,Enumerable,Writable的参数的
let descriptor = Object.getOwnPropertyDescriptor(persion,"age_");
console.log(descriptor.value);
console.log(descriptor.configurable);
console.log(descriptor.Enumerable);
console.log(descriptor.Writable);
descriptor = Object.getOwnPropertyDescriptor(persion,"age");
console.log(descriptor.configurable);
5,关于合并对象
平常使用Object.assign()方法进行操作,其实关于复制属于浅复制(只会复制对象的引用)
let dest, src, result;
dest = {id:3,name:'王五'};
src = {id:2,name:'李四'};
result = Object.assign(dest,{id:1,name:'张三'},src);
console.log(result); // 显示的是{id:2,name:'李四'}
// 如果对应的key值不一样的话,那就会进行合并
dest = {id:1,name:'王五'};
src = {age:2,bor:'李四'};
result = Object.assign(dest,src);
console.log(result); // {id: 1, name: '王五', age: 2, bor: '李四'}
增强的对象语法
// 可以得知,只要当前方法有对应的值,就可以进行套用
let name = "张三";
let persion{
name
}
console.log(persion); // {name: '张三'}
// 代码压缩为防止找不到
function makePersion(name){
return {
name
}
}
let perion1 = makePersion("张三");
console.log(persion1); // {name: '张三'}
// 对象定义方法的简写,
let persion2 = {
sayName : function(name){
console.log(name);
},
// 也可以这么写
sayName2(name){
console.log(name);
}
}
persion2.sayName("张三");
persion2.sayName2("李四");
对象解构
对象解构的意思,我的理解就是对对象的属性进行拆分访问
let persion = {
name : 'zhangsan',
age : 18
}
let {name,age} = persion;
console.log(name); // zhangsan
console.log(age); // 18
// 如果解构的信息不在对象中,则展示undefined
let {sex} = persion;
console.log(sex); // undefined
// 可以通过对象的方式进行嵌套结构
let copypersion = {};
({
name : copypersion.name,
age : copypersion.age
} = persion);
console.log(copypersion); // {name: 'zhangsan', age: 18}
// 如果涉及对象中套用对象的值进行更改的话,会发生变化
let persion1 = {
sex : {title:'man'}
}
let copyp1 = {};
({
sex:copyp1.sex
} = persion1);
console.log(copyp1.sex.title); // man
persion1.sex.title = 'woman';
console.log(copyp1.sex.title); //woman
6,创建对象
关于创建对象有两种方式,一种通过工厂方法(也就是带返回值),一种是通过构造器的方式
// 通过工厂的方法
function createPersion(name,age){
Object p1 = new Object;
p1.name = name;
p1.age = age;
p1.sayname = function(){
console.log(this.name);
}
return p1;
}
// 通过构造器的方式
function Persion(name,age){
this.name = name;
this.age = age;
this.sayname = function(){
console.log(this.name);
}
}
let p1 = createPersion("zhangsan",18);
let p2 = new Persion("lisi",19);
p1.sayname();
p2.sayname();
关于创建对象的步骤
1,在内存中创建一个新对象。
2,这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
3,构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
4,执行构造函数内部的代码(给新对象添加属性)。
5,如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
6,在调用一个函数而没有明确设置 this 值的情况下(即没有作为对象的方法调用,或 者没有使用 call()/apply()调用),this 始终指向 Global 对象(在浏览器中就是 window 对象)
关于原型模式(prototype)
每个函数都会创建prototype,这个属性是一个对象,包含应该由特定引用类型的实例 共享的属性和方法。它的好处是可以共享属性和方法
// 使用函数表达式也可以 let perison = funcition(){};
function persion(){}
persion.prototype.name = 'zhangsan';
persion.prototype.age = function(){
console.log("this is age");
}
let p1 = new persion();
let p2 = new persion();
console.log(p1.name); // zhangsan
console.log(p2.age()); // this is age
学习原型
只要创建一个函数就会创建prototype属性(指向原型对象)所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。persion.prototype.constructor 指向 persion
function Persion(){}
// 声明函数后,就会有与之对应的prototype的属性
console.log(Persion.prototype);
// 通过下图可以发现存在两个值,contructor和Prototype
// 通过下面三个可知,contructor这个构造函数属于Persion,并且Persion属于Object
// Object的原型是null,没有对应的指向
console.log(Persion.prototype.constructor === Persion) // true
console.log(Persion.prototype.__proto__ === Object.prototype) // true
console.log(Persion.prototype.__proto__.__proto__=== null) // true
理解原型
我们知道原型的属性和方法是共享的,但是具体怎么找的原来是什么呢?其实是在创建一个对象的时候,对象会去自己实例去找对应的属性,如果找不到的话则会去原型里面找,所以我们看到,当对象Persion.prototype.name = 'zhangsan'时,p1没有对应的属性,则会在原型中寻找,如果设置了则会去实例寻找对应的值。
function Persion(){};
function Persion1(){};
Persion.prototype.name = 'zhangsan';
let p1 = new Persion();
let p2 = new Persion();
p1.name = 'lisi';
console.log(p1.name); // lisi(从实例中拿取)
console.log(p2.name); // zhangsan(从原型拿取)
// 我们可以使用isPrototypeOf的方法判断他们是否属于同一个原型
console.log(Persion.prototype.isPrototypeOf(p1)); // true,可知他们属于一个原型
console.log(Persion1.prototype.isPrototypeOf(p1)); // false 同理
// 如果想从原型拿取属性,则需要对属性进行删除
delete p1.name;
console.log(p1.name); // zhangsan(从原型拿取)
理解 hasOwnProperty()方法
该方法用于确定某个属性是在实例上还是在原型对象上。这个方法是继承自 Object的
function Persion(){};
Persion.prototype.name = 'zhangsan';
let p1 = new Persion();
let p2 = new Persion();
p1.name = 'zhangsan';
console.log(p1.hasOwnProperty('name')); // true 说明属于实例上面的
console.log(p2.hasOwnProperty('name')); // false 说明属于原型上面的
关于继承
继承是面向对象的一种,包含接口和方法继承,前者是基于签名,后者则基于实际的方法,在ECMAScript只包含方法继承。而方法继承是通过原型链的方式处理的。
理解一下构造函数,实例,和原型之间的关系和含义
每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针(prototype)指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针(_ proto _)指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。
关于下面代码的理解
下面这段代码的解释有点小绕,大体个人理解为,创建的p1对象[[prototype]]指向的是subType,因为是共享原型的,但是subType的内部[[prototype]]指向的是superType的原型。也就是说他们共享的是superType的原型,新增的getValue方法是在当前的原型上进行增加的。所以只属于subType,在superType的原型中是找不到的.(顺便说一下他们的终端是Object.prototype)所以关于p1.getSuperValue()是现在p1实例找,找不到去subType里的原型找,找不到的话会去superType的原型去找。
// 创建父类函数
function superType(){
this.properties = true;
}
// 设置父类的原型方法
superType.prototype.getSuperValue = function(){
return this.properties;
}
// 创建子类函数
function subType(){
this.properties1 = false;
}
// 子类的原型执行父类的实例,也就理解为原型共享了getSuperValue的方法内容
subType.prototype = new superType();
// 必须要在子类原型指向父类后再创建此方法,要不原型变了是找不到getValue的方法的
subType.prototype.getValue = function(){
return this.properties1;
}
let p1 = new subType();
// 如果此时,subType中的properties = false,会覆盖supperType的properties。所以改成properties1则可以进行找到父类的properties值
console.log(p1.getValue()); // false
console.log(p1.getSuperValue()); // true
console.log(subType.prototype); // 其实他的原型就是指向superType的
console.log(superType.prototype);
原型链的问题 原型链遇到的问题就是属性共享,继承的时候,属性无法私有化。当p1和p2创建subType对象后,由于原型属性共享,导致p1新增内容,在p2中也可以看到。
function superType(){
this.color = ['read','blue'];
}
function subType(){};
// 子类的原型指向父类
subType.prototype = new superType();
let p1 = new subType();
p1.color.push('black');
let p2 = new subType();
console.log(p1.color); // ['read', 'blue', 'black']
console.log(p2.color); // ['read', 'blue', 'black']
要想解决上面的方法,可以利用call()或者apply()的方法进行处理。也就是类似的复制对象所有内容,不走原型链的操作。这个方法不能继承原型的东西,所以没啥意义。
function superType(){
this.color = ['read','blue'];
}
function subType(){
superType.call(this);
};
let p1 = new subType();
p1.color.push('black');
let p2 = new subType();
console.log(p1.color); // ['read', 'blue', 'black']
console.log(p2.color); // ['read', 'blue']
组合继承
也就是组合上面两种方法的合体,既能用原型链,又可以编辑属性。
可以发现继承的方法可以用原型获取,并且属性也隔离了。
function supperType(name){
this.name = name;
this.color = ['read'];
}
supperType.prototype.sayName = function(){
console.log(this.name);
}
function subType(name,age){
supperType.call(this,name);
this.age = age;
}
subType.prototype = new supperType();
subType.prototype.sayAge = function(){
console.log(this.age);
}
let p1 = new subType('zhangsang',18);
p1.color.push('blue');
let p2 = new subType('lisi',19);
console.log(p1.sayName()); // zhangsan
console.log(p1.sayAge()); // 18
console.log(p1.color); // ['read', 'blue']
console.log(p2.sayAge()); // 19
console.log(p2.sayName()); // lisi
console.log(p2.color); // ['read']
关于类
在es5通过函数的方式进行继承比较繁琐,es6推出类的概念
// 类的声明
class Persion();
// 类的表达式
const Perison = class{};
先了解继承吧,感觉和java很像,也当学习一下,先简单了解一下继承extends,可以适用于类和构造函数
class persion{}
class p1 extends persion{}
let p2 = new p1();
console.log(p2 instanceof p1); // true
console.log(p2 instanceof persion); // true
// 使用构造函数也是可以的
function p3(){};
class p4 extends p3{};
let p5 = new p4();
console.log(p5 instanceof p4); // true
console.log(p5 instanceof p3); // true
派生类的方法的this会体现在自己的实例上面。这个没啥可说的
class persion{
sayHello(name){
console.log(name,this);
}
static hello(name){
console.log(name,this);
}
}
class p1 extends persion{};
let p2 = new p1();
console.log(p2.sayHello("p2")) // p2 p1对象
p1.hello("p1"); // p1 p1对象
persion.hello("persion"); // persion persion对象
关于supper()的使用,只能在派生类中使用,而且仅 限于类构造函数、实例方法和静态方法内部。
class Persion{
constructor(){
this.color = ['read'];
}
// 父类必须要用静态方法,子类才能进行调用
static sayHello(){
console.log("hello");
}
}
class p1 extends Persion{
constructor(){
super(); // 调用父类的constructor
console.log(this instanceof Persion); // true
}
}
new p1(); // true
// 基于静态方法测试
class p2 extends Persion{
static sayHello(){
super.sayHello(); // 调用父类方法,返回hello字段
}
}
p2.sayHello(); // hello
supper使用的注意事项
1,super 只能在派生类构造函数和静态方法中使用。
2,不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法。
3,调用 super()会调用父类构造函数,并将返回的实例赋值给 this。
4,super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
5,如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数(也就是说继承类没有创建构造函数,但是会调用父类的构造函数)
class Persion{
constructor(){
this.color = ['read'];
console.log(111);
}
// 父类必须要用静态方法,子类才能进行调用
static sayHello(){
console.log("hello");
}
}
class p1 extends Persion{
}
new p1(); // 会打印111
6,在类构造函数中,不能在调用 super()之前引用 this。
7,如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象。