2018.10.15 星期一 11:31
对象初始化
可以通过new Object(), Object.create()方法,或者使用字面量标记(初始化标记)初始化对象。
私有/公有/共有-属性/方法,静态属性/方法,特权方法
私有:是指属性和方法不能在new对象中读写;
静态:无需实例化就可以调用的方法就叫静态方法;
受保护:不能直接读写,只能通过一个包装方法对其进行操作;
1、构造函数内部通过this声明的属性就是公有属性,通过var声明的就是私有属性。
2、所有定义在原型上方法都是“公有”的。
私有:只在对象内部能够访问,在构造函数内部直接调用var或function定义
公有:可以在对象外部访问,在构造函数内部用this定义
共有:所有对象公用的变量或方法,是公有的,在构造函数的prototype原型上定义
1 | var Book = function() { |
而要了解js面向对象,就必需先了解js中什么是公有方法、特权方法、静态方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// ## 1 公有属性和公有方法
// ## 2 私有属性和方法
// ### 3 静态属性和方法
// 在php中,无需实例化就可以调用的方法就叫静态方法,js也一样,无需实例化,即用new操作符实化对象,就可调用对象的方法和属性。
function User(){}
User.age = 26;//静态属性
User.myname = 'fire子海';
User.getName =function(){//静态方法
return this.myname;//如果这里使用this.name,返回的将是User,所有改用了myname,
}
console.log(User.getName());//output:fire子海
// ### 4 特权方法
function User(name,age){
var name = name;//私有属性
var age = age;
this.getName = function(){ //特权方法
return name;//私有属性和方法不能使用this调用
}
}
var user = new User('fire子海',26);
console.log(user.getName());//output:fire子海
// ## 5 静态类
// 对于静态方法和静态属性,我们无需像第三步中那样去创建,如果网友看过我那篇“js如何制作图片轮播”,就知道可以使用字面量的方式来创建。
var user = {
init:function(name,age){
this.name = name;
this.age = age;
},
getName:function(){
return this.name;
}
}
user.init('fire子海',26);
console.log(user.getName());//output:fire子海
## 6公有方法的调用规则
调用公有方法,我们必需先实例化对象 公有方法中通过this调用公有属性和特权方法,不能使用this调用静态方法和属性,必需裁通过对象本身调用,即对象名。公有方法也不能调用私有方法
使用静态方法时,无需实例化对象,便可以调用,对象实例不能调用对象的静态方法,只能调用实例自身的静态属性和方法。
特权方法通过this调用公有方法、公有属性,通过对象本身调用静态方法和属性,在方法体内直接调用私有属性和私有方法。
对象的私有方法和属性,外部是不可以访问的,在方法的内部不是能this调用对象的公有方法、公有属性、特权方法的
继承与原型链
当谈到继承时,JavaScript 只有一种结构:对象。
每个实例对象(object )都有一个私有属性(称之为[[prototype]])指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象 ,层层向上直到一个对象的原型对象为 null。
根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的Object的实例。
基于原型链的继承
1 | // ### 继承属性 |
使用不同的方法来创建对象和生成原型链
语法结构创建的对象
1 | // ## 1 |
构造器创建的对象
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
在g被实例化时,g.[[Prototype]]指向了Graph.prototype
Object.create 创建的对象
1 | var b = Object.create(a); |
class 关键字创建的对象
使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
注意:检查属性是否undefined还不够。该属性可能存在,但其值恰好设置为undefined。
性能
而不是其原型链上的某个属性,则必须使用所有对象从Object.prototype继承的 hasOwnProperty 方法。
错误实践:扩展原生对象的原型
经常使用的一个错误实践是扩展 Object.prototype 或其他内置原型。
扩展内置原型的唯一理由是支持JavaScript 引擎的新特性,如Array.forEach。
prototype和Object.getPrototypeOf
prototype是用于类的,而 Object.getPrototypeOf() 是用于实例的(instances),两者功能一致。
面向对象OOP-Tarena
OOP三大特点: 封装 继承 多态
原型链和继承
*继承: 父对象的成员(属性+方法),子对象不用重复创建,就可直接使用!
为什么继承: 代码重用,节约内容——优点
原型对象: 专门保存一类子对象,共有成员的父对象。
原型对象不用手动创建!
何时使用原型对象:今后,只要所有子对象,公用的属性和方法,都要放在原型对象中。
原型链(prototype chain):由各级父对象的proto属性,逐级继承,形成的链式结果
原型链控制着对象的属性的使用顺序:
优先使用当前对象本地的属性——自有属性;
如果自己没有,则延原型链向父级查找。
如果整个原型链上都没有,才返回undefined
## 利用原型链:
1. 今后,只要所有子对象共有的属性和方法,都要集中定义在原型对象中。
2. 为内置对象,扩展新属性——解决浏览器兼容性问题。
### vs 作用域链:控制函数中变量的使用顺序
通常,作用域链中只包含两种对象:函数作用域(AO)和全局作用域(window)
原型和属性API
1.获得原型对象3种方式:
1.通过构造函数获得:
构造函数.prototype
2.通过子对象获得:child.__proto__
内部属性
Object.getPrototypeOf(obj):获得obj的父级原型对象。
2.判断自有属性和共有属性:
1 | // ## 1 自有属性: 判断"属性名"是否直接保存在obj对象本地。 |
修改属性
一般属性定义在哪儿,就用哪个对象修改该属性的值:
修改自有属性: obj.属性名=值;
修改共有属性: 构造函数.prototype.属性名=值
强行用子对象,修改原型对象中的属性:
删除对象的属性: delete obj.属性名
只能删除自有属性。
要删除共有属性,必须删除原型对象的属性。
### $PS_多态:同一个方法,在不同时刻,表现出不同的状态。
重写(override):如果子对象觉得父对象继承来的方法不好用,可在本地定义同名自有方法,覆盖父对象的方法。
为什么:为了体现子对象和父对象之间的差异!
继承的几种方式
构造函数的继承
一、 构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
二、 prototype模式
第二种方法更常见,使用prototype属性。
如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。
三、 直接继承prototype
第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
四、 利用空对象作为中介
五、 拷贝继承1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50function Animal(){
this.species = "动物";
}
function Cat(name,color){
this.name=name;
this.color=color;
}
// ## 一、 构造函数绑定
function Cat(name,color){
Animal.apply(this,arguments)
}
// ## 二、 prototype模式
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// ## 三、 直接继承prototype
function Animal(){}
Animal.prototype.species='动物'
Cat.prototype= Animal.prototype
Cat.prototype.constructor=Cat // 际上把Animal.prototype对象的constructor属性也改掉了!
// $PS: ES6
function Rectangle() {
Shape.call(this); // call super constructor.
}
Rectangle.prototype=Object.create(Shape.prototype); // ES6
Rectangle.prototype.constructor = Rectangle;
// ## 四、 利用空对象作为中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
// ### 封装
function extend(Child,Parent){ //就是YUI库如何实现继承的方法。
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Cat;
Child.uber = Parent.prototype; // 可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,
// 纯属备用性质。
}
// ## 五、 拷贝继承
function extend2(Child,Parent){
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
非构造函数的继承
1 | var Chinese={nation:'中国'} |
Object构造函数
Object构造函数为给定值创建一个对象包装器。如果给定值是 null 或 undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。
当以非构造函数形式被调用时,Object 等同于 new Object()。
Object构造函数的属性
Object.length 值为1。
Object.prototype 可以为所有 Object 类型的对象添加属性。
Object构造函数的方法
Object.assign():通过复制一个或多个对象来创建一个新的对象。
Object.create():使用指定的原型对象和属性创建一个新对象。
Object.defineProperty():给对象添加一个属性并指定该属性的配置。
Object.defineProperties():给对象添加多个属性并分别指定它们的配置。
Object.setPrototypeOf():设置对象的原型(即内部[[Prototype]]属性)。
Object.getPrototypeOf():返回指定对象的原型对象。
Object.getOwnPropertyNames():返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertySymbols():返回一个数组,它包含了指定对象自身所有的符号属性。
Object.getOwnPropertyDescriptor():返回对象指定的属性配置。
Object.is():比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。
Object.isExtensible():判断对象是否可扩展。
Object.isFrozen():判断对象是否已经冻结。
Object.isSealed():判断对象是否已经密封。
Object.preventExtensions():防止对象的任何扩展。
Object.freeze():冻结对象:其他代码不能删除或更改任何属性。
Object.seal():防止其他代码删除对象的属性。
Object.entries():返回给定对象自身可枚举属性的[key, value]数组。
Object.keys():返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.values():返回给定对象自身可枚举值的数组。
$PS: Object.create和new的区别? (有什么意义吗)
Object.create使用现有的对象来提供新创建的对象的proto。
返回一个新对象,带着指定的原型对象和属性。
如果propertiesObject参数不是 null 或一个对象,则抛出一个 TypeError 异常。
Object 实例和Object 原型对象
JavaScript中的所有对象都来自Object;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。例如,其他构造函数的原型将覆盖constructor属性并提供自己的toString()方法。Object原型对象的更改将传播到所有对象,除非受到这些更改的属性和方法将沿原型链进一步覆盖。
属性
Object.prototype.constructor 特定的函数,用于创建一个对象的原型。Object.prototype.__proto__
指向当对象被实例化的时候,用作原型的对象。Object.prototype.__noSuchMethod__
当未定义的对象成员被调用作方法的时候,允许定义并执行的函数。
方法
Object.prototype.hasOwnProperty(): 返回一个布尔值 ,表示某个对象是否含有指定的属性,而且此属性非原型链继承的。
Object.prototype.isPrototypeOf(): 返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable(): 判断指定属性是否可枚举,
Object.prototype.toString(): 返回对象的字符串表示。
Object.prototype.toLocaleString(): 直接调用 toString()方法。
Object.prototype.toSource() : 返回字符串表示此对象的源代码形式,可以使用此字符串生成一个新的相同的对象。
Object.prototype.valueOf(): 返回指定对象的原始值。
Object.prototype.watch() : 给对象的某个属性增加监听。
Object.prototype.unwatch() : 移除对象某个属性的监听。
[创建对象的7种方式]
最近在复习红宝书的对象一章,红宝书中一共提到了7种创建对象的方式(这里所说的对象更偏向于面向对象编程中的对象)。7种方式分别是:
工厂模式
构造函数模式
原型模式
构造函数和原型组合模式
动态原型模式
寄生构造模式
稳妥构造模式
<!–
1. 工厂模式
缺点:对象无法识别,因为所有的实例都指向一个原型1
2
3
4
5
6
7
8
9function createPerson(name) {
var o = new Object();
o.name = name;
o.getName = function () {
console.log(this.name);
};
return o;
}
var person1 = createPerson('kevin');
2. 构造函数模式
优点:实例可以识别为一个特定的类型
缺点:每次创建实例时,每个方法都要被创建一次1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function Person(name) {
this.name = name;
this.getName = function () {
console.log(this.name);
};
}
var person1 = new Person('kevin');
// 2.1 构造函数模式优化
function Person(name) {
this.name = name;
this.getName = getName;
}
function getName() {
console.log(this.name);
}
var person1 = new Person('kevin');
// 优点:解决了每个方法都要被重新创建的问题
// 缺点:这叫啥封装……
3. 原型模式
优点:方法不会重新创建
缺点:1. 所有的属性和方法都共享 2. 不能初始化参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39function Person(name) {
}
Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
console.log(this.name);
};
var person1 = new Person();
// 3.1 原型模式优化
function Person(name) {
}
Person.prototype = {
name: 'kevin',
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
// 优点:封装性好了一点
// 缺点:重写了原型,丢失了constructor属性
// 3.2 原型模式优化
function Person(name) {
}
Person.prototype = {
constructor: Person,
name: 'kevin',
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
// 优点:实例可以通过constructor属性找到所属构造函数
// 缺点:原型模式该有的缺点还是有
4. 组合模式
构造函数模式与原型模式双剑合璧。
优点:该共享的共享,该私有的私有,使用最广泛的方式
缺点:有的人就是希望全部都写在一起,即更好的封装性1
2
3
4
5
6
7
8
9
10
11
12function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
4.1 动态原型模式
1 | function Person(name) { |
5.1 寄生构造函数模式
寄生构造函数模式,我个人认为应该这样读:
寄生-构造函数-模式,也就是说寄生在构造函数的一种方法。
也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用 instanceof 都无法指向构造函数!1
2
3
4
5
6
7
8
9
10
11
12
13function Person(name) {
var o = new Object();
o.name = name;
o.getName = function () {
console.log(this.name);
};
return o;
}
var person1 = new Person('kevin');
console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object) // true
这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,我们可以这样写:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function SpecialArray() {
var values = new Array();
for (var i = 0, len = arguments.length; i < len; i++) {
values.push(arguments[i]);
}
values.toPipedString = function () {
return this.join("|");
};
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');
console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
你会发现,其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候,多使用了一个new,实际上两者的结果是一样的。
但是作者可能是希望能像使用普通 Array 一样使用 SpecialArray,虽然把 SpecialArray 当成函数也一样能用,但是这并不是作者的本意,也变得不优雅。
在可以使用其他模式的情况下,不要使用这种模式。
但是值得一提的是,上面例子中的循环:1
2
3for (var i = 0, len = arguments.length; i < len; i++) {
values.push(arguments[i]);
}
可以替换成:values.push.apply(values, arguments);
5.2 稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。
与寄生构造函数模式有两点不同:
1) 新创建的实例方法不引用 this
2) 不使用 new 操作符调用构造函数
稳妥对象最适合在一些安全的环境中。
稳妥构造函数模式也跟工厂模式一样,无法识别对象所属类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function person(name){
var o = new Object();
o.sayName = function(){
console.log(name);
};
return o;
}
var person1 = person('kevin');
person1.sayName(); // kevin
person1.name = "daisy";
person1.sayName(); // kevin
console.log(person1.name); // daisy
–>
15:55