1、面向对象
什么是对象
程序中描述现实中一个具体事物的属性和方法的结构。其本质就是,在内存中保存多个属性和方法的一块存储空间,再起一个名字。----> 相当于变量。
对象是一个容器,封装了属性(property)和方法(method)
属性:对象的状态
方法:对象的行为
对象的成员: 成员 = 属性 + 方法
访问对象成员:对象名.属性名 或者 对象名["属性名"] ---> 如果访问的属性不存在时,不会报错,返回undefined
调用对象方法: 对象名.方法名() 或者 对象名["方法名"]()
面向对象
OOP(面向对象程序设计),程序中,描述一具体事物都要用对象来封装事物的属性和方法。即在写任何程序时,都需要先找对象,再识别对象的属性和方法。
面向对象的特性
封装性、继承性、多态性
封装
将描述一个事物的属性和方法集中定义在一个对象中。便于反复调用,代码重用和便于维护
继承
父对象中的属性和方法,子对象可直接使用。
在js中所有的继承都是通过原型(_proto_)实现的。能使得代码重用,便于维护,以及节约内存空间
多态(重写和重载)
同一个东西在不同情况下,表现为不同的形态
面向对象的设计思想
- 抽象出 Class(构造函数)
- 根据 Class(构造函数) 创建 Instance(实例)
- 指挥 Instance 得出结果
构造函数中 new 关键字的原理
- 创建新的空对象,将构造函数中的this 指向 新对象
- 让新对象 自动 继承 构造函数 的原型对象
- 执行构造函数的语句,向对象中添加新成员并赋值
- 返回新对象地址保存在变量中
function Person(name,age) {
// var instance = new Object(); // 创建空对象
// this = instance; // 将this指向新对象
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
// return instance; // 将新对象返回
}
2、原型
prototype 原型对象
任何函数都具有一个 prototype 属性,该属性是一个对象,在定义构造函数时自动创建
- 可以在原型对象上添加属性和方法
- 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
- 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针_proto_
- 实例对象可以直接访问原型对象成员
原型对象的使用
可以根据成员的不同功能进行相应地设置:
对于私有成员(一般就是非函数成员),直接放到构造函数中
-
对于共享成员(一般就是函数),就需要放在构造函数的原型对象中,同时,在添加原型对象的属性和方法时,有以下几种方式:
- 将所有实例共享的属性和方法,都添加给原型对象,如:
Fn.prototype.成员属性(或方法) = XXX
- 或者,直接使用一个对象字面量对原型对象进行赋值,如:
Fn.prototype = { constructor:Fn, 成员属性:XXX, 成员方法:XXX }
- 将所有实例共享的属性和方法,都添加给原型对象,如:
构造函数、实例、原型对象三者之间的关系
3、 原型链
由多级对象逐级继承形成的链式结构,即由各级子对象的_proto_属性连续引用形成的结构。所有对象都有__proto__
,所有对象原型链的顶端都是Object.prototype
保存了所有对象的成员(属性和方法)
原型链查找机制
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是否具有给定名字的属性,查找过程:
- 首先从对象实例本身开始查找
- 如果在实例中找到了具有给定名字的属性,则返回该属性的值
- 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
- 如果在原型对象中找到了这个属性,则返回该属性的值
- 若在原型链上都没有,则返回undefined
原型链示例图
function Person(){}
var p1 = new Person()
自定义继承
-
1、仅修改两个对象间的继承关系
child.__proto__ = father
-
Object.setPrototypeOf(child,father)
---> 让child的原型 继承 father的原型
缺点:一次只能修改一个对象的继承关系
-
2、修改构造函数的原型对象(prototype) ,为新的父对象 ---> 必须在创建子对象前修改
步骤:
1、构造函数.prototype = 新父对象;
2、构造函数.prototype.constructor = 构造函数 自身// 定义新父对象 var father = { // ... } // 定义构造函数 function Child(){} // 构造函数的原型改为新父对象 Child.prototype = father; // 恢复构造函数Child的constructor属性指向自身构造函数 Child.prototype.constructor = Child;
-
3、 用一个已有的父对象作为参照,创建一个新的子对象,同时,扩展子对象的自由属性(本方法实际只是创建对象的方法,不是继承方法)----
Object.create(obj_proto,propertiesObject)
该方法两个参数:
obj_proto
,指定新创建对象的原型对象propertiesObject
,可省略,指定新创建对象的 自有属性(改参数是个对象集,可设置对个属性,其属性类型与Object.defineProperties()
的第二个参数类似)返回值:一个新对象,带有指定的原型对象和属性。
用法var child = Object.create(father); // 此时,实际完成了两步:创建空对象child;设置child 的 __proto__指向 father
var child = Object.create(father,{ prop1:{ writable:true, // 是否可修改,可省略 value: 属性值, // ... 其他属性描述符 }, prop2:{ // ... } });
-
4、两种类型间的继承 --- 组合继承,即继承结构,又继承原型
如果两种类型间有部分相同的属性结构和方法,则:1、定义抽象父类型
父类型的构造函数中定义公共的属性
父类型原型中定义公共方法2、 在子类型构造函数中借用父类型构造函数,使用call/apply,将父类型构造函数中的this临时替换为子类型
父类型构造函数.call(this,参数列表) 或者 父类型构造函数.apply(this,[参数列表])
---> 完成这一步,只是借用父类型构造函数的语句,并没有实现对象间的继承-
3、定义子类构造函数之后,设置子类型构造函数的原型对象prototype继承父类型的原型对象。
Object.setPrototypeOf(子类型.prototype,父类型.prototype)
或者 子类构造函数.prototype = new 父类构造函数对象的实例function A(){} function B(){ // 继承父类A的属性 A.call(this); } // 子类B的原型继承父类A的原型 Object.setPrototypeOf(B.prototype,A.prototype); // 或者 B.prototype = new A(); // 恢复B的constructor属性指向自身构造函数 B.prototype.constructor = B;
共有属性 和 自有属性
共有:添加到原型对象中,所有子对象共有的属性,即从原型链中继承来的属性
自有:直接保存到对象自身的属性
属性的访问
- 读取属性
共有和自有两者用法一致----->对象.属性
先从对象自身查找,若找不到该属性,则向父级原型找
判断对象的属性是自有,还是共有:
1、判断自有属性:
obj.hasOwnProperty("属性名")
,返回值为布尔值,表示 该属性 是否是 obj对象 自身的属性
如果返回false,只能说明 obj对象不存在 该属性,并不能说明该属性是共有,因为有可能原型链中也不存在-
2、判断在 原型链中 是否有 指定属性:
+"属性名" in 对象
,如: if("name" in Person){} ---> if语句能执行,则说明存在- if(obj["属性名"]){} ----> 如果if语句能执行,则 存在(不过可能存在缺陷,如果属性值刚好 为:0,NaN,null,"",undefined,false时,就无效了)
判断共有属性,固定套路:
if (Object.hasOwnProperty("属性名")) {
console.log("自有属性");
} else if ("属性名" in obj) {
console.log("共有属性")
} else {
console.log("不存在该属性")
}
获取任意子对象的原型
obj.__proto__
(个别浏览器禁用)var prototype = Object.getPrototypeOf(obj)
此方法用于:在无法获得构造函数时,又希望设置子对象的共有属性
判断父对象是否在子对象的原型链上
-
父对象.isPrototypeOf(子对象)
该方法,不仅找直接父级,而且找整个原型链
一道鄙视题:typeof失效----typeof 只能判断原始类型的值,无法判断引用类型的值,所以 对Array属性失效
如何判断一个对象 obj 是 数组Array 类型
1、可以判断 Array对象原型 是否在 obj 对象的原型链上
if(Array.prototype.isPrototypeOf(obj)){
// obj 是 数组类型
}
2、由于构造函数的prototype指向原型对象,同时,原型对象上有constructor 指回构造函数对象(排除constructor被修改的情况)
if(obj.constructor === Array){
// obj 是 数组类型
}
3、通过判断子对象 是否 是指定构造函数的实例
// instanceof 也是查找整个原型链
if(obj instanceof Array){
// obj 是 数组类型
}
4、通过获取对象的原型,再判断该对象原型 是否 为 数组对象原型
// instanceof 也是查找整个原型链
if(Object.getPrototypeOf(obj) === Array.prototype){
// obj 是 数组类型
}
5、通过call、apply(两者可以返回任何对象的构造函数),使用Object的原型对象的方法 toString(),判断返回的类型
if(Object.prototype.toString.call(obj) == "[object Array]"){
// obj 是 数组类型
}
6、ES5中的新函数: Array.isArray(obj)
返回值为布尔值