6.面向对象
6.1.对象
- 创建:
new Object() || { }
6.1.1.对象的属性类型
-
数据属性:
-
Configurable:属性是否允许配置(delete,修改)
var person = {}; // "use strict"; Object.defineProperty(person, "name", { configurable: false, value: "ming" }); Object.defineProperty(person, "name", { writable: false, value: "mi" });
-
Enumerable:属性是否可以通过for-in返回属性
-
Writable:属性是否可以修改
-
Value:属性值的读写
-
调用
Object.defineProperty()方法时如果不指定前三个属性,则全部设为false
-
-
访问器属性:常见使用方式( 设置一个值的属性会导致其他属性发生变化 )
-
Configurable:属性是否允许配置(delete,修改)
-
Enumerable:属性是否可以通过for-in返回属性
-
Get:读取属性
-
Set:写入属性
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function () { return this._year; }, set: function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition);
-
只指定getter意味着属性不能写( 严格模式报错 )
-
只指定setter意味着不能读( 严格模式undefined )
-
6.1.2.定义多个属性
// 方法: defineProperties()
var book = {};
Object.defineProperties(book, {
_year: { // 数据属性,以_开头表示该属性只能通过对象方法访问
value: 2004
},
edition: { // 数据属性
value: 1;
},
year: { // 访问器属性
get: function() {
return this._year;
},
set: function() {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
6.1.3.读取属性的特性
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); // 2004
alert(descriptor.configurable); // false
6.2.创建对象
6.2.1.工厂模式
-
抽象了创建具体对象的过程
-
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; o.greet = function () { console.log("hi"); } return o; } var person1 = createPerson("Vinson", 18);
-
缺点:没有解决对象识别的问题
6.2.2.构造函数模式
-
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); } } var person1 = new Person("ming", 18); var person2 = new Person("mei", 20);
-
以new的方式创建会经历4个步骤
- 创建一个新对象
- 将构造函数的作用域赋给新对象( 因此this就指向了这个新对象 )
- 执行构造函数中的代码( 为这个对象添加属性 )
- 返回新对象
-
创建出来的对象保存着Person的一个实例,且有一个constructor(构造函数)属性,该属性指向Person
person1.constructor == Person; // true person2.constructor == Person; // true
-
检测类型
person1 instanceof Object; // true 因为继承于Object person1 instanceof Person; // true
-
将构造函数当作函数
// 当作构造函数使用 var person = new Person("ming", 18); person.sayName(); // ming // 当作普通函数用 Person("ming", 18); window.sayName(); // ming // 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "ming", 18); o.sayName(); // ming
-
构造函数的问题
-
每个方法都要在每个实例上创建一遍
person1.sayName == person2.sayName; // false
-
如果让所有实例对象共享一个全局作用域中定义的同一个函数,这解决了上面的问题,但是如果对象需要定义多个方法,那么就没有封装性可言了
-
6.2.3.原型模式
-
function Person(){}; Person.prototype.name = "ming"; Person.prototype.age = 29; Person.prototype.sayName = function () { alert(this.name); }
-
每一个函数都有一个prototype属性,它是一个指针,指向实现了所有实例对象的共享方法和属性的原型对象
-
所有的原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针
Person.prototype.constructor 指向 Person
-
创建一个实例后,该实例内部将包含一个指针_proto__,指向构造函数的原型对象
-
确定对象原型
alert(Person.prototype.isPrototypeOf(person1)); // true alert(Person.prototype.isPrototypeOf(person2)); // true // ES5 新增 alert(Object.getPrototypeOf(person1) == Person.prototype); // true alert(Object.getPrototypeOf(person2) == Person.prototype); // true
-
对象属性读取搜索( 也是多个对象实例共享原型保存的属性和方法的原理 )
- 对象实例本身
- 对象的原型
- 对象的原型的原型
- Null
-
属性重写
- 如果在实例中添加一个于与原型对象中重名的属性,则该属性会屏蔽访问原型中的属性
-
检测一个属性是否在对象实例中
person1.hasOwnProperty("name"); // 若是只存在于原型中,则返回false
-
原型与in操作符
for in
for-in
会遍历实例和原型中所有可枚举的属性( 注意屏蔽了原型中不可枚举属性的实例属性也会在for-in 中返回, IE8及之前不会返回 )
in
判断属性是否存在于实例或者原型中,都会返回true
-
判断属性是否存在于原型中
function hasPrototypeProperty(object, name){ retrun !object.hasOwnProperty(name) && (name in object); } // 注意屏蔽的情况,虽然原型中有,但依然返回false
-
取得对象中的实例属性
- 所有可枚举:
Object.keys(person1);
,返回字符串数组 - 所有:
Object.getOwnPropertyNames();
,返回字符串数组
- 所有可枚举:
-
更简单的原型写法
function Person(){}; Person.prototype = { name = "ming"; age = 29; sayName = function () { alert(this.name); } }
-
本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性,指向Object构造函数,不再指向Person函数
var friend = new Person(); alert(friend instanceof Object); // true alert(friend instanceof Person); // true alert(friend.constructor == Person); // false alert(friend.constructor == Object); // true
-
解决方法
Person.prototype = { constructor: Person; name = "ming"; age = 29; sayName = function () { alert(this.name); } }
-
以这种方式重设construtor属性会导致它的[Enumerable]属性被设置为true,而默认情况下原生的constructor属性是不可枚举的
-
解决办法
Object.defineProperty(Person.prototype, "constructor", { enumberable: false; value: Person })
-
-
-
原型的动态性
function Person(){}; var friend = new Person(); Person.prototype = { constructor: Person, name: "ming", age: 18, sayName: function() { alert(this.name); } }; friend.sayName(); // error
- 重写原型对象切断了现有原型与任何值钱已经存在的对象实例之间的联系,他们引用的仍然是最初的原型
-
原生对象的原型
alert(typeof Array.prototype.sort); // function alert(typeof String.prototype.substring); // function
- 也可以用来添加新方法,但不推荐(可能会导致命名冲突的问题)
-
原型对象的问题
-
没有为构造函数传递舒适化参数,结果导致所有实例默认情况下都将取得相同的属性值,带来不便
-
所有实例共享引用类型,但实例一般都是要有属于自己的属性
function Person(){}; Person.prototype = { constructor: Person, name: "ming", age: 18, friends: ["ming", "mei"], sayName: function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("hong"); alert(person2.friends);
-
6.2.4.组合使用构造函数模式和原型模式
- 构造函数模式用于定义实例属性
- 原型模式用于定义方法和共享的属性
- 优点:最大限度的节省内存,集构造函数模式和原型模式之长
6.2.5.动态原型模式
-
将构造函数模式和原型模式封装在一起
function Person(name, age) { this.name = name; this.age = age; if(typeof this.sayName != "function"){ Person.prototype.sayName = function() { alert(this.name); }; Person.prototype.greet = function() { alert("hello"); } } }
6.2.6.寄生构造函数模式*
6.2.7.稳妥构造函数模式*
6.3.继承
6.3.1.原型链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jc5ezeZa-1591007014309)(/Users/vinsonsun/Desktop/Develop/WebDevelop/webFrontNote/图片/屏幕快照 2020-04-20 下午6.32.41.png)]
-
将子类的原型设置为父类的新实例
-
属性搜索机制
- 对象实例本身
- 对象的原型
- 对象的原型的原型
- …
- Object
- null
-
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也是所有自定义类型都会继承toString( ),valueOf( )等默认方法的根本原因
-
确定原型和实例的关系
alert(instance instanceof Object); // true alert(instance instanceof SuperType); // true alert(instance instanceof Subtype); // true alert(Object.prototype.isprototypeOf(instance)); // true alert(SuperType.prototype.isprototypeOf(instance)); // true alert(SubType.prototype.isprototypeOf(instance)); // true
-
谨慎定义方法
- 添加新方法或者重写超类型中的方法要写在替换原型的语句之后
- 不能使用通过对象字面量创建原型方法
-
原型链的问题
-
所有实例共享引用类型,但实例一般都是要有属于自己的属性
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } // 原型继承 SubType.prototype = new SuperType(); var instance1 = new Subtype(); var instance2 = new Subtype(); instance1.colors.push("black"); alert(instance2.colors);
-
创建自类型实例时,不能向超类型的构造函数中传递参数
-
6.3.2.借用构造函数
function SuperType(name) {
this.name = name;
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
var instance = new SubType("ming", 18);
- 借用构造函数的问题
- 无法实现函数复用和方法属性共享
6.3.3.组合继承
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
}
function SubType(name, age) {
// 继承实例属性方法
SuperType.call(this, name);
this.age = age;
}
// 继承原型属性方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
}
- js中最常用的继承模式
6.3.4.原型式继承*
6.3.5.寄生式继承*
6.3.6.寄生式组合继承*
6.3.7.Class继承#
6.4.对象拷贝
6.4.1.浅拷贝
-
浅拷贝只拷贝一层,引用类型只拷贝引用(地址)
-
循环实现
for (var k in o1) { o2[k] = obj[k]; }
-
Object.assign(target, source)
:ES6新增
-
6.4.2.深拷贝
-
深拷贝拷贝多层,全部拷贝
-
递归实现
function deepCopy (target, source) { for (var k in source) { //判断属性值的数据类型 var item = source[k]; //判断是否是数组(要写在Object上面,因为Array也是Object) if (item instanceof Array) { target[k] = []; deepCopy (target[k], item); } else if (item instanceof Object) { target[k] = {}; deepCopy (target[k], item); } else { target[k] = item; } } }
-
JSON
方法实现var target = JSON.parse(JSON.stringify(source));
-