javascript中面向对象中对象,属性,原型链和一些扩展知识总结

面向对象和面向对象编程


面向对象


就是找个工具,帮我完成一项工作,对象就像一个工具一样,每个工具都可以帮我们实现某个功能,比如汽车可以实现运输,我们只需要学会如何去开动汽车,而不需要知道汽车是如何实现开动的。面向对象的好处:我们所有的项目都是团队作战。通过对象实现任务分离。用对象的某一部分实现一项功能,可以实现团队作战。对象实质上就是一个包含多个工具的工具包,用对象来实现对功能的分类管理。

比如一个字符串对象,有toString,substring,subStr,indexOf,lastIndexOf等函数,用来实现不同的功能。

在对象中可以包含属性,方法,通过点语法来访问对象的属性和方法。


面向对象编程


相当于组装一辆汽车。汽车是一个对象,我们需要让汽车有移动,刹车等功能。我们先要定义这个对象,就是构造函数。然后给汽车添加轮胎,发动机等,就相当于添加属性,我们让汽车跑起来,刹车,就相当于添加方法。而一个刹车系统,相当于对象中的一个模块。我们通过模块化来管理整个对象。这就是面向对象编程。而在我们给别人使用这个对象的时候,只需要让别人实例化这个对象,然后就可以访问对象中的属性和对象中的方法了。


对象的几种不同表现形式


下面列举几个,更多请看我之前的博客:http://blog.csdn.net/tyro_java/article/details/51031006

json对象
var person = {"name":"tom","age":"10"}
此处用json实现一个person对象,json对象是对象的一种字面量表示形式

扩展知识:

json对象和json字符串之间的转换:
通过JSON.parse(str),把字符串转换成json对象,通过JSON.stringify(obj)把对象转换成字符串。

json对象和json协议之间的区别:
json协议是一种国际标准,用于前端与后端进行交换数据的格式。是一种规范,按照这种规范就能实现数据的传递。json对象是对象的字面量表示形式,相当于一个实例化后的对象。

用构造函数实现的对象
function Person (name,age) {
  this.name = name;
  this.age = age;
}
var person = new Person('Tom',10); // 实例化对象
console.log(person.name); // 此处我们通过点语法来访问对象的属性
console.log(person.age);
通过构造函数和原型来实现对象

function Person (name,age) {
  this.name = name;
  this.age = age;
}
Person.prototype = {
  say:function() {
    console.log(this.name + ' is saying');
  }
}
var person = new Person('Tom',10);
person.say(); // Tom is saying
扩展知识:通常,我们都用此种方法来实例化对象,为了节省内存,我们把对象的方法都放在原型里面。为什么呢? 在我们通过new实例化对象的时候,在内存中,会自动拷贝构造函数中的所有属性和方法,用来给实例对象赋值,而不管我们实例化多少次,原型里面的属性和方法只生成一次,所以会节省内存。我们在实例化new的时候,系统做了如下几件事情:

a.在内存中创建了一个空的对象,就像是这样 var p = {};
b.拷贝对象中的属性和方法到空对象中。
c.自动生成一个__proto__属性指向类的原型。
有关c我们可以通过如下来验证。
console.log(person.__proto__ === Person.prototype); // true
另外,由此引出两个方法 hasOwnProperty() 和 isPrototypeOf()方法
hasOwnProperty:用来判断某个方法或者属性是否属于该对象,备注:只会去寻找对象构造函数或者json对象里面的属性和方法,还用person举例:
// 用三个if来 测试 hasOwnProperty
if(person.hasOwnProperty('name')){
  console.log('name exsit'); // 输出 name exsit
}
if(person.hasOwnProperty('say')){
  console.log('say exsit'); // 不输出
}
if(Person.prototype.hasOwnProperty('say')){
  console.log('say exsit'); // 输出 say exsit
}
hasOwnProperty 此方法不会检查该对象的原型链,被检查的属性和方法必须是对象本身的一个成员。
比如第二if,它不会检查原型里的say方法,只会在构造函数中去找,所以不输出。
比如第三个if中用Person.prototype中,有输出,是因为Person.perototype本身就是一个对象,它在自己里面能找到say方法。
isPrototypeOf:它是基类对象Object的一个方法:Object.prototype.isPrototypeOf()。
先做个示例:obj1.isPrototypeOf(obj2),它的意思是obj1是否被包含在obj2的原型链中,下面是示例代码:
// 先创建4个构造函数
function A() {}
function B() {}
function C() {}
function D() {}

B.prototype = new A(); // 把B的原型指向A实例化后的对象
C.prototype = new B(); // 把C的原型指向B实例化后的对象
D.prototype = new C(); // 把D的原型指向C实例化后的对象

// 下面开始来测试
var d = new D();
if (B.prototype.isPrototypeOf(d)) {
  console.log('haha'); // 输出 haha
}
从这里可以看出 isPrototypeOf 和 instanceof 的区别,我们开始详细的说下:

instanceof : mdn 上的api详解:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof

isPrototypeOf : mdn 上的api详解:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/IsPrototypeOf

简要的说:instanceof运算符可以用来判断某个构造函数的prototype属性所指向的對象是否存在于另外一个要检测对象的原型链上。
isPrototypeOf() 方法测试一个对象是否存在于另一个对象的原型链上。

function A() {}
function B() {}
function C() {}
function D() {}

B.prototype = new A(); // 把B的原型指向A实例化后的对象
C.prototype = new B(); // 把C的原型指向B实例化后的对象
D.prototype = new C(); // 把D的原型指向C实例化后的对象

var d = new D();
var c = new C();
var b = new B();
var a = new A();


console.log(A.prototype.isPrototypeOf(d)); // true 这里A是处于顶层,表示 A.prototype处于d的原型链上
console.log(B.prototype.isPrototypeOf(d)); // true
console.log(C.prototype.isPrototypeOf(d)); // true
console.log(D.prototype.isPrototypeOf(d)); // true


console.log(d instanceof C); // true
console.log(d instanceof B); // true
console.log(d instanceof A); // true
object instanceof AFunction  检测的是AFunction.prototype是否在object的原型链中,而不是检测AFunction自身。
语法:object instanceof constructor

isPrototypeOf 方法允许你检查一个对象是否存在于另一个对象的原型链上。
语法:prototype.isPrototypeOf(object)

属性进阶


万物皆属性


方法从某种角度来说,也可以看成一个属性,方法的定义,使用,在内存中的存储,都基本一致。
function Test(){
  this.name = 'test';
  this.test = function() {
    console.log('test');
  }
}

万物皆变量


所有的数据都是通过变量来保存(管理)的,变量都是在内存中存储的
var str = 'javascript';
var json = {"name":"javascript"}
var fn = function() {}
扩展知识:函数声明和函数表达式的区别  函数声明会被提升,函数表达式不会被提升,请看下面代码:
function fn() {}
var func = function(){};
上面的代码实际解析的顺序是这样的:
var func;
function fn(){};
func = function(){};
有关提升的问题,请参考我之前的博文:http://blog.csdn.net/tyro_java/article/details/51131812

属性分类


公有属性、私有属性、公有静态属性、类方法、原型方法、原型属性
function Car(type,price) {
  this.type = type; // 公有属性
  this.price = price;
  var private = 1; // private 是私有属性,外界或者原型里都无法访问,只在构造函数内部能访问
  this.run = function() { // 对象方法
    console.log('run');
  }
}
Car.prototype = {
  color:'black',// 原型属性
  drive:function() { // 原型方法
    console.log('drive');
  },
  test:function() {
    console.log(private);
  }
}
Car.Weight = 1000; // 公有静态属性,只能通过构造函数来访问,首字母一般大写
Car.Fly = function(){ // 类方法 ,只能通过构造函数来访问,首字母一般大写
  console.log('car can\'t fly');
};

// 调用测试
var c = new Car('small',10000);
console.log(c.run()); // run
console.log(c.color); // black
console.log(Car.prototype.color); // black
console.log(c.drive()); // drive
console.log(Car.Weight); // 1000
console.log(Car.Fly()); // car can't fly
get,set取值器和设置器,用于对某一属性进行包装,用于取值和设置,js原生自带的两种。因为这个比较复杂我找到了一篇api文档,详细说明了这个,还有 Object.defineProperty() 方法的介绍。
文档如下:https://msdn.microsoft.com/zh-cn/library/dd548687

对象实例进阶


在进行这个话题之前,我们需要了解一下,实例化后的对象是如何在内存中存储的。
当我们实例化的时候,会在内存里面开辟两段区域:

栈专门存储简单变量,堆专门用来存储对象。
当我们实例化的时候,会在内存中开辟一段区域,然后自动拷贝构造函数中所有的属性和方法。

虽然都是通过同一个对象new出来的,但是不同的实例,会被分配到不同的空间,当我们更改一个实例的属性时,不会影响到另一个实例。
但是有一点,如果你更改的是在原型里的引用类型的属性,那么这个引用类型属性时共享的,更改一个,会影响其他实例。
原因是原型里面的引用类型属性是全部共享的,值类型不会共享,举例如下:
function Classroom(id,num,teacher) {
  this.id = id;
  this.num = num;
  this.teacher = teacher;
}
Classroom.prototype = {
  property:{
    blackbord:"only one",
    chalk:"110pieces"
  },
  other:'hehe'
}
var cr = new Classroom(3,2,'Tom');
cr.property.blackbord = 'has two'; // 修改引用类型的property属性
cr.other = 'crhehe'; // 修改值类型的other属性
var cr2 = new Classroom(1,2,'Jack');
console.log(cr2.property.blackbord); // has two ,这里其它实例受到影响了
console.log(cr2.other); // hehe, 这里其它实例不受影响。
每个实例对象都有两个隐藏的属性一个是__proto__,一个是constructor。实例的__proto__属性来自它的构造函数,实例的constructor来自它的构造函数的原型里,下面用浏览器的 repl 环境下测试来验证这一说法,截图验证:

javascript中面向对象中对象,属性,原型链和一些扩展知识总结_第1张图片

原型链进阶


原型


在js中只要是一个对象都会有一个原型,也就是prototype,那么js设计者为什么会这样设计呢,因为这样设计有个好处,就是节省内存。那么,我们再次来说下,实例化一个对象的时候,系统都做了哪些事情:

1.创建一个空的对象 

2.拷贝构造函数中的属性和方法放到空对象里

3.自动生成一个__proto__属性指向类的原型


因为对象的方法中大都是大片的代码块,如果都放在构造函数里面就会有大量的内存开支,因为每次我们new的时候,都会重复上面三个步骤,而prototype里的东西只在第一次new的时候生成,不会每次都生成,所以我们需要把那些比较占用内存的方法放到原型里面。

原型对象的特性

1.不管实例化多少次,原型对象只生成一次。

2.原型对象中的属性和方法可以被所有实例访问。

3.原型对象中引用类型的属性是共享的,值类型的属性是各自拥有的。

(在我之前的博文中有提到: http://blog.csdn.net/tyro_java/article/details/50990135 ,看实践二和实践三)

4.对象实例化后实际上生成了两个对象,一个是构造函数对象,一个是原型对象。


原型链


我们知道,类的构造函数和类的原型(prototype)之间有一个联系,就是类的构造函数中的一个隐藏属性__proto__ ,它指向了这个类的prototype。
而类的prototype里面也有一个隐藏属性constructor,指向了这个类的构造函数。
另外类的prototype里面还有一个隐藏属性__proto__ 指向了这个类的父类,由此实现了链条关系。
还在浏览器的repl环境里举个例子:


    javascript中面向对象中对象,属性,原型链和一些扩展知识总结_第2张图片


内置对象的原型链

分析:上面str是一个String对象的一个实例,实例有两个隐藏属性 __proto__ 和constructor

str.constructor === String.prototype.constructor   为true表示,实例的隐藏属性constructor 来自类的原型。 
str.__proto__ === String.prototype   为true ,表示实例的__proto__指向类的原型
str.constructor === String 和 String.prototype.constructor === String   都为true 表示constructor指向类的构造函数
String.prototype.__proto__ === Object.prototype   为true 表示了继承关系,String类原型中的__proto__指向了父类Object的原型

属性和方法的搜索机制

实例化后的对象首先遍历自身的属性和方法,如果没有找到,就通过类构造函数中的__proto__去类的原型里去找,如果在类原型里面还没有找到,那么按着类的原型中的__proto__这个链条到上一级的类的原型中找,一直按着这种关系,直到搜索到Object.prototype,如果还没找到返回undefined,在系统中最终的链条是null,Object.prototype.__proto__ === null ,所以说null 是JavaScript面向对象的源头。
阶段性总结一下:js通过__proto__ 这个链条将构造函数和原型以及一层一层的原型直到最终的null联系起来,这就是所谓的 原型链

① Object对象的__proto__ :Object对象是Function对象的一个实例 如下所示:
console.log(Object.constructor === Function); // true  
console.log(Object.__proto__ === Function.prototype); // true 
②不止是Object,在js中所有的内置对象都是Function的一个实例,如下所示:
console.log(String.constructor === Function); // true  
console.log(String.__proto__ === Function.prototype); // true  
  
console.log(Array.constructor === Function); // true  
console.log(Array.__proto__ === Function.prototype); // true  
  
console.log(Image.constructor === Function); // true  
console.log(Image.__proto__ === Function.prototype); // true  
// ... and so on  
  
console.log(Number.__proto__ === Function.prototype); // true  
console.log(Boolean.__proto__ === Function.prototype); // true  
console.log(String.__proto__ === Function.prototype); // true  
console.log(Object.__proto__ === Function.prototype); // true  
console.log(Function.__proto__ === Function.prototype); // true  
console.log(Array.__proto__ === Function.prototype); // true  
console.log(RegExp.__proto__ === Function.prototype); // true  
console.log(Error.__proto__ === Function.prototype); // true  
console.log(Date.__proto__ === Function.prototype); // true  
console.log(Image.__proto__ === Function.prototype); // true
③ 而内置对象的实例,则各自指向各自的原型,如下所示:
var str = new String('hello');  
var arr = new Array(1,2,3);  
var img = new Image();  
console.log(str.__proto__ === String.prototype); // true;  
console.log(arr.__proto__ === Array.prototype); // true;  
console.log(img.__proto__ === Image.prototype); // true; 
④ 将随便挑一个比如String对象把它单独拿出来看,会有这么一条关系链,如下:
var str = new String('hello');
console.log(str.__proto__ === String.prototype); // true
console.log(String.prototype.__proto__ === Object.prototype); // true
console.log(String.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

自定义对象的原型链

function A() {};
var a = new A();
console.log(a.__proto__ === A.prototype); // true
console.log(A.constructor === Function); // true
console.log(A.__proto__ === Function.prototype); // true
console.log(Function.constructor === Function); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
总结null是链条的最终的源头。
Object.prototype是一切链式查找的最后一站。
Function.prototype是一切内置函数和自定义构造函数的原型对象。
而属性搜索机制的底层就是通过__proto__属性链接起来的。所以__proto__才是面向对象的底层实现机制,是理解面向对象本质所在。

你可能感兴趣的:(Javascript)