Js对象

2018.10.15 星期一 11:31

对象初始化

可以通过new Object(), Object.create()方法,或者使用字面量标记(初始化标记)初始化对象。

私有/公有/共有-属性/方法,静态属性/方法,特权方法

私有:是指属性和方法不能在new对象中读写;
静态:无需实例化就可以调用的方法就叫静态方法;
受保护:不能直接读写,只能通过一个包装方法对其进行操作;

1、构造函数内部通过this声明的属性就是公有属性,通过var声明的就是私有属性。
2、所有定义在原型上方法都是“公有”的。

私有:只在对象内部能够访问,在构造函数内部直接调用var或function定义
公有:可以在对象外部访问,在构造函数内部用this定义

共有:所有对象公用的变量或方法,是公有的,在构造函数的prototype原型上定义

1
2
3
4
5
6
7
8
9
10
11
12
13
var Book = function() {
var num = 1; // 私有属性
function addNum(){num++;} // 私有方法

this.name = "javscript"; // 公有属性
this.getName = function() {return this.name} // 公有方法
}

Book.isBook = true; // 静态公有属性(对象实例不能访问)
Book.checkBooks = function() { } // 静态公有方法(对象实例不能访问)

Book.prototype.calcPrice = function() {return this.price * this.num;} // 实例共有方法
Book.prototype.sell = true // 实例共有属性

而要了解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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ### 继承属性
// 让我们假设我们有一个对象 o, 其有自己的属性 a 和 b:
// {a: 1, b: 2}
// o 的 [[Prototype]] 有属性 b 和 c:
// {b: 3, c: 4}
// 最后, o.[[Prototype]].[[Prototype]] 是 null.
// 这就是原型链的末尾,即 null,
// 根据定义,null 没有[[Prototype]].
// 综上,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> null

// ### 继承方法
// 当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
var p = Object.create(o);
// p是一个继承自 o 的对象

使用不同的方法来创建对象和生成原型链

语法结构创建的对象

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
// ## 1
var o = {a: 1};
// o 这个对象继承了Object.prototype上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:o ---> Object.prototype ---> null

// ## 2 数组都继承于 Array.prototype
var a = ["yo", "whadup", "?"];
// (Array.prototype 中包含 indexOf, forEach等方法)
// 原型链如下: a ---> Array.prototype ---> Object.prototype ---> null

// ## 3 函数都继承于Function.prototype
// (Function.prototype 中包含 call, bind等方法)
function f(){
return 2;
}
// 原型链如下:f ---> Function.prototype ---> Object.prototype ---> null

// $PS:
var o={}
o.constructor === Object // true
o.__proto__ === Object.prototype // true
o.prototype // undefined
Object.prototype.__proto__=== null // true
Function.prototype.__proto__=== Object.prototype // true
Array.prototype.__proto__=== Object.prototype // true

构造器创建的对象

在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
在g被实例化时,g.[[Prototype]]指向了Graph.prototype

Object.create 创建的对象

1
2
3
4
5
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

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
2
3
4
5
6
7
// ## 1 自有属性: 判断"属性名"是否直接保存在obj对象本地。
var bool=obj.hasOwnProperty("属性名");
// ## 2 共有属性: 不是自有,而且在原型链上有
!obj.hasOwnProperty("属性名")&&(obj.属性名!==undefined)
// ## 3 判断原型链上是否包含指定属性:
obj.属性名!==undefined // 方法1
var bool= "属性名" in obj // 方法2

修改属性

一般属性定义在哪儿,就用哪个对象修改该属性的值:
修改自有属性: 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
50
function 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
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
var Chinese={nation:'中国'}
var Doctor={career:'医生'}

// ## $l六:object
functio object(o){
function F(){}
F.prototype=o
return new F()
}
var Doctor = object(Chinese);
Doctor.career = '医生';

// ## $七:浅拷贝 extendCopy
function extendCopy(p){
var c={}
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
// ## $八:深拷贝 jQuery库使用的就是这种继承方法
function deepCopy(p,c){
var c=c||{}
for (var i in p) {
if(typeof p[i]=== 'object){
c[i] = (p[i].constructor===Array?[]:{});
deepCopy(p[i],c[i])
}else{
c[i] = p[i];
}
}
return c;
}

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
9
function 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
19
function 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
39
function 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
12
function Person(name) {
this.name = name;
}

Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};

var person1 = new Person();

4.1 动态原型模式

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
Person.prototype.getName = function () {
console.log(this.name);
}
}
}

var person1 = new Person();

/*************************/
// 注意:使用动态原型模式时,不能用对象字面量重写原型
// 解释下为什么:
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
}
}
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

// 报错 并没有该方法
person1.getName();

// 注释掉上面的代码,这句是可以执行的。
person2.getName();

// 为了解释这个问题,假设开始执行var person1 = new Person('kevin')。

/* 如果对 new 和 apply 的底层执行过程不是很熟悉,可以阅读底部相关链接中的文章。

我们回顾下 new 的实现步骤:

首先新建一个对象
然后将对象的原型指向 Person.prototype
然后 Person.apply(obj)
返回这个对象
注意这个时候,回顾下 apply 的实现步骤,会执行 obj.Person 方法,这个时候就会执行 if 语句里的内容,注意构造函数的 prototype 属性指向了实例的原型,使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值,person1 依然是指向了以前的原型,而不是 Person.prototype。而之前的原型是没有 getName 方法的,所以就报错了!
*/
/*************************/

// 如果你就是想用字面量方式写代码,可以尝试下这种:
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
}

return new Person(name);
}
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

person1.getName(); // kevin
person2.getName(); // daisy

5.1 寄生构造函数模式

寄生构造函数模式,我个人认为应该这样读:
寄生-构造函数-模式,也就是说寄生在构造函数的一种方法。

也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用 instanceof 都无法指向构造函数!

1
2
3
4
5
6
7
8
9
10
11
12
13
function 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
20
function 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
3
for (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
15
function 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

knowledge is no pay,reward is kindness
0%