js面向对象理解

简介: js面向对象理解

ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript 没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。

js(如果没有作特殊说明,本文中的js仅包含ES5以内的内容)本身是没有class类型的,但是每个函数都有一个prototype属性。prototype指向一个对象,当函数作为构造函数时,prototype则起到类似class的作用。


一.创建对象


创建一个对象,然后给这个对象新建属性和方法。

1. var box = new Object(); //创建一个Object 对象
2. box.name = 'Lee'; //创建一个name 属性并赋值
3. box.age = 100; //创建一个age 属性并赋值
4. box.run = function () { //创建一个run()方法并返回值
5. return this.name + this.age + '运行中...';
6. };
7. alert(box.run()); //输出属性和方法的值

上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box 对象本身。这种是JavaScript 创建对象最基本的方法,但有个缺点,想创建多个类似的对象,就会产生大量的代码。

为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。

1. function createObject(name, age) { //集中实例化的函数
2. var obj = new Object();
3. obj.name = name;
4. obj.age = age;
5. obj.run = function () {
6. return this.name + this.age + '运行中...';
7. };
8. return obj;
9. }
10. var box1 = createObject('Lee', 100); //第一个实例
11. var box2 = createObject('Jack', 200); //第二个实例
12. alert(box1.run());
13. alert(box2.run()); //保持独立

工厂模式解决了重复实例化的问题,但是它有许多问题,创建不同对象其中属性和方法都会重复建立,消耗内存;还有函数识别问题等等。


二.构造函数的方法


构造函数的方法有一些规范:

1)函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和

普通函数);

2)通过构造函数创建对象,必须使用new 运算符。

1. function Box(name, age) { //构造函数模式
2. this.name = name;
3. this.age = age;
4. this.run = function () {
5. return this.name + this.age + '运行中...';
6. };
7. }
8. var box1 = new Box('Lee', 100); //new Box()即可
9. var box2 = new Box('Jack', 200);
10. alert(box1.run());
11. alert(box1 instanceof Box); //很清晰的识别他从属于Box

构造函数可以创建对象执行的过程:

1)当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();

2)将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this 就

代表new Object()出来的对象。

3)执行构造函数内的代码;

4)返回新对象(后台直接返回)。

注:

1)构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。

2)this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

这种方法解决了函数识别问题,但消耗内存问题没有解决。同时又带来了一个新的问题,全局中的this 在对象调用的时候是Box 本身,而当作普通函数调用的时候,this 又代表window。即this作用域的问题。


三.原型


我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype 通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

1. function Box() {} //声明一个构造函数
2. Box.prototype.name = 'Lee'; //在原型里添加属性
3. Box.prototype.age = 100;
4. Box.prototype.run = function () { //在原型里添加方法
5. return this.name + this.age + '运行中...';
6. };

构造函数的声明方式和原型模式的声明方式存储情况如下:

所以,它解决了消耗内存问题。当然它也可以解决this作用域等问题。

我们经常把属性(一些在实例化对象时属性值改变的),定义在构造函数内;把公用的方法添加在原型上面,也就是混合方式构造对象(构造方法+原型方式):

1. var person = function(name){
2. this.name = name
3.   };
4.   person.prototype.getName = function(){
5. return this.name; 
6.   }
7. var zjh = new person(‘zhangjiahao’);
8.   zjh.getName(); //zhangjiahao

下面详细介绍原型:

1.原型对象

  每个javascript对象都有一个原型对象,这个对象在不同的解释器下的实现不同。比如在firefox下,每个对象都有一个隐藏的__proto__属性,这个属性就是“原型对象”的引用。

2.原型链

  由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个就是原型链,JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined.原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。

1)__proto__和prototype

JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子为例:

console.log(zjh.__proto__ === person.prototype) //true

同样,person.prototype对象也有__proto__属性,它指向创建它的函数对象(Object)的prototype

console.log(person.prototype.__proto__ === Object.prototype) //true

继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null

console.log(Object.prototype.__proto__) //null

我们把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。如下图:

2)constructor

 原型对象prototype中都有个预定义的constructor属性,用来引用它的函数对象。这是一种循环引用

1. person.prototype.constructor === person //true
2. Function.prototype.constructor === Function //true
3. Object.prototype.constructor === Object //true

3)为加深对理解,我们再举一个例子:

1. function Task(id){  
2.     this.id = id;  
3. }  
4. 
5. Task.prototype.status = "STOPPED";  
6. Task.prototype.execute = function(args){  
7. return "execute task_"+this.id+"["+this.status+"]:"+args;  
8. }  
9. 
10. var task1 = new Task(1);  
11. var task2 = new Task(2);  
12. 
13. task1.status = "ACTIVE";  
14. task2.status = "STARTING";  
15. 
16. print(task1.execute("task1"));  
17. print(task2.execute("task2"));

结果:

1. execute task_1[ACTIVE]:task1
2. execute task_2[STARTING]:task2

构造器会自动为task1,task2两个对象设置原型对象Task.prototype,这个对象被Task(在此最为构造器)的prototype属性引用,参看下图中的箭头指向。

由于Task本身仍旧是函数,因此其”__proto__”属性为Function.prototype, 而内建的函数原型对象的”__proto__”属性则为Object.prototype对象。最后Obejct.prototype的”__proto__”值为null。

总结:

实例对象的__proto__指向,其构造函数的原型;构造函数原型的constructor指向对应的构造函数。构造函数的prototype获得构造函数的原型。

有时某种原因constructor指向有问题,可以通过

constructor:构造函数名;//constructor : Task

重新指向。


四.继承


继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript 只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。

在JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)

1.call+遍历

属性使用对象冒充(call)(实质上是改变了this指针的指向)继承基类,方法用遍历基类原型。

1. function A()
2. {
3.     this.abc=12;
4. }
5. 
6. A.prototype.show=function ()
7. {
8.     alert(this.abc);
9. };
10. 
11. //继承A
12. function B()
13. {
14. //继承属性;this->new B()
15.     A.call(this);   //有参数可以传参数A.call(this,name,age)
16. }
17. 
18. //继承方法;B.prototype=A.prototype;
19. for(var i in A.prototype)
20. {
21.     B.prototype[i]=A.prototype[i];
22. }
23. //添加自己的方法
24. B.prototype.fn=function ()
25. {
26.     alert('abc');
27. };
28. 
29. var objB=new B();
30. var objA=new A();objB.show();

可以实现多继承。

2.寄生组合继承

主要是Desk.prototype = new Box(); Desk 继承了Box,通过原型,形成链条。主要通过临时中转函数和寄生函数实现。

临时中转函数:基于已有的对象创建新对象,同时还不必因此创建自定义类型

寄生函数:目的是为了封装创建对象的过程

 

1. //临时中转函数
2. function obj(o) { //o表示将要传递进入的一个对象
3. function F() {}   //F构造是一个临时新建的对象,用来存储传递过来的对象
4. F.prototype = o;  //将o对象实例赋值给F构造的原型对象
5. return new F();   //最后返回这个得到传递过来对象的对象实例
6. }
7. //寄生函数
8. function create(box, desk) {
9. var f = obj(box.prototype);
10. f.constructor = desk;  //调整原型构造指针
11. desk.prototype = f;
12. }
13. function Box(name) {
14. this.name = name;
15. this.arr = ['apple','pear','orange'];
16. }
17. Box.prototype.run = function () {
18. return this.name;
19. };
20. function Desk(name, age) {
21. Box.call(this, name);
22. this.age = age;
23. }
24. //通过寄生组合继承实现继承
25. create(Box, Desk); //这句话用来替代Desk.prototype = new Box();
26. var desk = new Desk('Lee',100);
27. desk.arr.push('peach');
28. alert(desk.arr);
29. alert(desk.run());

临时中转函数和寄生函数主要做的工作流程:

临时中转函数:返回的是基类的实例对象函数

寄生函数:将返回的基类的实例对象函数的constructor指向派生类,派生类的prototype指向基类的实例对象函数(是一个函数原型),从而实现继承。

相关文章
|
7月前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(三)
深入JS面向对象(原型-继承)
58 0
|
7月前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
66 0
js- 面向对象进阶
Object.defineProperty等面向对象的信息
|
存储 JavaScript 前端开发
|
6月前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
155 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
|
7月前
|
前端开发 JavaScript
前端 JS 经典:Class 面向对象
前端 JS 经典:Class 面向对象
40 1
|
7月前
|
JavaScript 前端开发
JavaScript 原型链继承:掌握面向对象的基础
JavaScript 原型链继承:掌握面向对象的基础
|
7月前
|
JavaScript 前端开发 API
在Node.js上使用dojo库进行面向对象web应用开发
请注意,虽然这个例子在Node.js环境中使用了Dojo,但Dojo的许多功能(例如DOM操作和AJAX请求)在Node.js环境中可能无法正常工作。因此,如果你打算在Node.js环境中使用Dojo,你可能需要查找一些适用于服务器端JavaScript的替代方案。
70 0
|
7月前
|
JSON JavaScript 前端开发
深入JS面向对象(原型-继承)(四)
深入JS面向对象(原型-继承)
51 0
|
7月前
|
设计模式 JavaScript 前端开发
深入JS面向对象(原型-继承)(二)
深入JS面向对象(原型-继承)
69 0