javascript面向对象的写法03

js一些基础知识的说明

prototype

首先每个js函数(类)都有一个prototype的属性,函数是类。注意类有prototype,而普通对象没有。
js中有一些常用的内置类。下面代码打印内置类和自定义类的protytype
console.log(String.prototype);
console.log(Date.prototype);
console.log(Object.prototype); function ClassA(name,job,born) {
}
console.log(ClassA.prototype);

prototype一般作用

prototype作用可以扩展类,为类添加一些属性或者方法的定义。
实际上prototype是类的一个特殊的固有的属性,它指向的是一个简单的普通的对象。因为被指向的是一个普通的对象,所以可以对prototype指向的对象增加属性或修改属性等。prototype指向的对象被更改过后,最后的效果类似把类的定义更改过。
修改prototype指向的对象实例。
Object.prototype.name = "111";    //为Object原型增加一个name属性
function ClassA(name,job,born) {
} ClassA.prototype.name = "123"; //自定义类原型也可以修改
ClassA.prototype.func1 = function(){}; //同样可以修改原型的func1属性为一个函数指针

prototype作用可以扩展类,比如为某个类的原型增加了一些属性,那么该修改过的类所创建的所有对象都有此属性。

function ClassA(name,job,born) {
//此时的ClassA类没有定义任何的属性,用它new出来的对象自然是没有name属性
}
var objB = new ClassA();
console.log(objB.name); //输出undefined Object.prototype.name = "111";
ClassA.prototype.name = "123"; //prototype的修改对之前new出来的对象同样生效 var objA = new Object();
console.log(objA.name); //内置类的prototype修改也是可以的 console.log(objB.name); //修改过prototype后便有这个属性了

  

可以把prototype看做类的另外一半,把prototype和原本的类并在一起就相当于一个完整的类了。

function ClassA() {
this.a = 100;
this.b = 100;
}
ClassA.prototype = {
'name': '123',
'getName': function(){}
}; //类似于
function ClassA() {
this.a = 100;
this.b = 100;
this.name = '123';
this.getName = function(){};
}

js访问一个对象的属性时,先在自己的属性中寻找,如果没有继而会去从类的prototype中找。自己定义的属性是会覆盖住类prototype定义的属性。

function ClassA() {
this.a = 100;
}
ClassA.prototype = {
'a': 200,
}; var obj = new ClassA();
console.log(obj.a); //输出100

prototype还可以修改已有的类

比如修改内置Array类的prototype,添加一个方法。
Array(数组)没有“是否包含”这样的方法,可以自己扩展一个
Array.prototype.containsObject = function(e) {
for (i=0; i<this.length; i++) {
if (this[i] == e) {
return true;
}
}
return false;
};

使用prototype的好处

使用prototype可以扩展类从而使得类创建的对象拥有扩展的属性或方法,类本身作为构造函数也可以为对象设置属性。
function ClassA() {
this.a = 100;
}
ClassA.prototype = {
'b': 200
};

那么两者有何区别呢?使用何种方式好。

首先类作为构造函数的写法是同以下形式作用一样,前面文章提到过。
function ClassA() {
this.a = 100;
} //两者等同
function ClassA() {
}
//
var obj = new ClassA();
obj.a = 100; //也就是如果有其他对象要创建的话就如下面一样
var obj02 = new ClassA();
obj02.a = 100;
var obj03 = new ClassA();
obj03.a = 100;
也就是说类构造函数方式创建的对象,每个对象都有一份所有属性的拷贝。这个无可厚非,对象的普通属性值在不同对象会不同,因该是每个对象一份拷贝的。
但如果属性是一个函数指针呢,也就是说每个对象的方法也都同普通属性一样,各自有一份拷贝。这个就显得不合理,因为函数的具体内容是一样的,只要存一个地方就行了,多少份拷贝没有任何区别。
所以使用prototype可以有更加完善的类的方法的写法。
function ClassA() {
this.func1 = function() {}; //此方法每个类都有一份拷贝。
}
ClassA.prototype.func2 = function(){}; //此方法存在于类的prototype属性中,只有一个地方有。 //调用func2的时候,obj对象自身没有,于是到类的prototype中寻找。
var obj = new ClassA();
obj.func2();

注意这两种写法的区别

function ClassA() {

}
//第一种方式扩展方法
ClassA.prototype.func1 = function() {};
ClassA.prototype.func2 = function() {}; //第二种,注意此种方法会有一些影响
ClassA.prototype = {
"func1": function() {},
"func2": function() {}
};
最关键的地方在于前者ClassA.prototype.func1是对ClassA.prototype的func1属性做操作,字面意思是如果有则修改为,没有则添加一个func1属性。
而后者则是将整个ClassA.prototype指向给替换掉了。替换成了一个{}对象了,{}对象是内置Object类的对象。
看这句更好理解ClassA.prototype = new ClassB()。
后者替换了类原始的prototype属性,后面会提到这种的影响,这个影响应当注意一下,在什么时候会有副作用自己要有所掌控。

constructor

每个js对象都有一个constructor属性,类也有,因为类也是对象,把类当对象使用的时候跟普通对象没区别。
constructor指向的是创建自身对象的构造函数,也就是自身的构造器是哪个。之前提过函数是对象的构造器,同时可以当成类来看待。所以constructor也可以理解成指向创建自己的类,也就是对象自己是由哪个类创建的。
function ClassA() {
} var obj = new ClassA();
console.log(obj.constructor); //这里输出的就是ClassA函数,也就是ClassA类了。 console.log(ClassA.constructor); //类(函数)也是对象,也有constructor。它的是Function

原型对象和原型链

每个对象都有它自己的原型对象。所谓的原型对象就是上面所说的,对象的类(构造函数)的prototype属性所指的那个对象。
请看下面的例子
function ClassA() {
}
var obj = new ClassA(); //obj的原型对象是什么? 是obj构造函数的prototype属性所指向的对象 obj.constructor == obj构造函数
obj.constructor.prototype == obj构造函数的prototype属性所指向的对象 == obj的原型对象
类(函数)也是对象,它也有自己的原型对象。
注意:函数的原型对象不是它的prototype属性所指的对象。而是它构造函数的prototype所指向的对象。请看下面例子
function ClassA() {
}
//ClassA是对象,它的原型对象是什么? ClassA.prototype != ClassA的原型对象
ClassA.constructor.prototype == ClassA的原型对象
函数的构造函数是Function类,因此类的原型对象是Function.prototype所指向的对象。
记住普通对象的原型对象公式:obj.constructor.prototype
原型链就很简单了,就是对象的原型对象也有原型对象,这样一直向上就是原型链的意思了。
当访问对象的某个属性时,如果当前对象没有此属性,那么就从它的原型对象中去寻找,如果再没有,就往原型链的方向查找每一个原型对象,直到Object.prototype指向的对象(顶层的原型对象了)。再没有抛错。
如何从任意一个对象(obj)找到它的祖辈的原型对象?
obj.constructor.prototype.constructor.prototype        //错误
obj.constructor.constructor.prototype //正确

对象靠内部的__proto__属性和原型对象关联。每个对象有个__proto__属性,此属性名字前有下划线就表明他是个内部变量,外部不可访问更加不能修改。

obj.__proto__ == obj.constructor.prototype == obj的原型对象

call和apply方法

最简单,call方法会执行一个函数。但是与简单的函数执行有点不同,下面会提到。

//最简单的使用call,调用一个普通的函数
function func1() {
console.log(func1);
}
func1.call();

call如果带参数则情况不同,call函数的第一个参数有特殊意义,它会将第一个参数传给被调用的函数里面,并且将被调函数里面的this指针切换到这个参数。

function func1() {
console.log(this); //这种情况下,这里的this指向的是下面的a。因为下面使用func1.call调用并传递了第一个参数a。
}
var a = {'b': 100};
func1.call(a); //func1函数会被执行,并且对象a会被传递到func1中,作为func1中this的执行。

如果call没有第一个参数,则默认传递window对象。

看一个稍微复杂点的例子,如果能明白各个方法的调用情况就对call理解了。

function A() {
this.bb = function(){
console.log(this);
};
} function ClassA() {
console.log(this);
this.bb(); //注意ClassA本身没有bb这个方法。
} var a = new A();
a.bb.call(); //调用a.bb方法。因为没有第一个参数,则传递window到a.bb方法中,所以a.bb里面的this指针指向的是window
ClassA.call(a); //调用ClassA函数,ClassA里面的this指针指向的是a对象。所以this有bb方法可以调用。

看下面的例子,因为call可以替换函数内的this对象,则可以达到如下效果。

function A() {
this.attr = 100;
this.func = function(){
};
} function B() {
A.call(this);
} var b = new B();
b.func();
console.log(b.attr);
上面的例子,类B没有定义任何属性和方法,但是它创建的对象b却可以有类A中定义的属性和方法。
A.call(this)调用的地方是类B内部。此时的this指向执行的是b对象,A.call(this)将a对象传递到函数A中,则此时A函数执行过程中的this指针指向了a对象,于是就给a赋值了属性和方法。
通过这样可以模拟出继承的效果,类B继承自类A。
apply
apply函数和call函数功能一样只是参数格式不同。以下的代码效果是一样的
function func(a, b, c, d) {
} func.call(obj, 1, 2, 3, 4);
func.apply(obj, [1, 2, 3, 4]); //第二个参数必须是一个数组。数组里面内容依次是调用func的实参。

除了参数格式外,其他没啥区别。但是因为参数格式不同,所以apply函数可以实现简化代码的作用。例如

function A(a, b, c) {
} function B(a, b, c) {
A.apply(this, arguments); //这里arguments为[1,2,3]。所以就直接传递了,不用自己组装。
} var a = new B(1, 2, 3);
还有一个妙用也可以简化代码。
许多函数被定义成了单个参数形式,Math.max(1,2,3)这样,使用apply则可以Math.max.apply(null, array)。当你获得某个数组的引用的时候,不需要讲数组通过下标一个个将参数再写一次了。

new操作符

new用来创建一个对象,要写面向对象方式的js必须用它。
他最简单的用法就是new 后面跟一个函数名创建一个对象,如果不使用new关键字,那么函数就当做普通的函数对待。
function a(){}

var b = a();    //当做普通函数对待,此时b的值为undefine
var b2 = new a(); //此时b2为一个对象,一个object空对象,没有什么其他自定义的属性。

new内部执行的一些操作

1.创建一个简单的object对象,空的对象就像{}。我们这里把它先叫做aa;
2.设置和原型对象的关联,上面提过原型对象是函数的prototype属性所指向的对象。
aa.__proto__ = Class.prototype;    //前面提到过,对象通过__proto__属性和原型对象关联。

3.执行constructor函数并且设置aa的constructor属性。前面提到过constructor指向的是构造函数(类),constructor函数执行过程中,函数内部会创建一个this指针,this指向到第一步创建的对象中。这样constructor构造函数内部就可以使用this关键字了。

constructor.apply(aa, [args]);    //执行apply方法,传递第一步的对象为第一个参数,则后面的this就是指向的aa.

4.将第一步创建的aa对象,或者说是this指向的对象返回。如果构建函数本身有返回值,则情况会有所不同。

4.1普通情况下返回第一步创建的对象

function ClassA() {
this.a = 100;
}
4.2如果构造函数本身有返回值,并且返回的是一个对象。
function ClassA() {
this.a = 100;
return {
"b": 200
};
} var obj1 = new ClassA();
var obj2 = ClassA(); //此种情况下,ClassA被当作普通函数执行,它里面的this指针指向是全局的对象window

上面此种情况用new操作符和直接执行函数返回的结果是一样的。都是{"b": 200}对象。使用new关键字的情况,前面几个步骤创建的对象会抛弃。

4.3构造函数有返回值,但是返回的不是一个对象,而是一个简单类型或是其他的,则跟没返回值一样,返回的还是this指针。
function ClassA() {
this.a = 100;
return 200; //因为是返回的200是简单类型,所以在使用new ClassA()的情况,此语句可以看做是被忽略了。
}

new关键字做的工作看以看似下面的几条语句

var obj = {};
obj.__proto__ = constructor.prototype;
constructor.apply(obj, [args]);

javascript面向对象的写法03的更多相关文章

  1. javascript面向对象的写法及jQuery面向对象的写法

    文章由来:jQuery源码学习时的总结 在JS中,一般的面向对象的写法如下: function Cao(){}//定义一个构造函数 Cao.prototype.init = function(){}/ ...

  2. javascript面向对象的写法01

    类和对象 其他面向对象的语言类的语法是内置的,自然而然的事.javascript中有对象,但没有类的语法,类的实现需要模拟出来. 只需要把对象想成一个容器,里面存放一些属性或方法,把类想象成一个对象的 ...

  3. javascript面向对象的写法02

    面向对象特性的初步实现 1.封装 利用作用域封装变量 作用域的概念是一样的,for语句,if语句等这些作用域内定义的变量只能作用域内访问,函数内定义的变量只能函数内访问. function Class ...

  4. [js高手之路] javascript面向对象写法与应用

    一.什么是对象? 对象是n个属性和方法组成的集合,如js内置的document, Date, Regexp, Math等等 document就是有很多的属性和方法, 如:getElementById, ...

  5. 03.JavaScript 面向对象精要--理解对象

    JavaScript 面向对象精要--理解对象 尽管JavaScript里有大量内建引用类型,很可能你还是会频繁的创建自己的对象.JavaScript中的对象是动态的. 一.定义属性 当一个属性第1次 ...

  6. 浅谈javascript面向对象

    我们常用的两种编程模式 POP--面向过程编程(Process-oriented programming) 面向过程编程是以功能为中心来进行思考和组织的一种编程方法,它强调的是系统的数据被加工和处理的 ...

  7. Javascript面向对象(封装、继承)

    Javascript 面向对象编程(一):封装 作者:阮一峰 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程( ...

  8. javascript面向对象(一):封装

    本文来自阮一峰 学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难.因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握. 下面就是我的学 ...

  9. JavaScript面向对象,及面向对象的特点,和如何构造函数

    1.面向对象和面向过程的区别 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了: 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是 ...

随机推荐

  1. 错误及异常处理-[PathTooLongException]指定的路径或文件名太长

    错误信息 System.IO.PathTooLongException:"指定的路径或文件名太长,或者两者都太长.完全限定文件名必须少于 260 个字符,并且目录名必须少于 248 个字符. ...

  2. C# LINQ学习笔记

    LINQ,语言集成查询: LINQ TO SQL,同EF,NHibernate一样,也是一种ORM框架: 1. 入门应用示例: static public void LinqBasic() { var ...

  3. pip升级最新版本

    1.如果是python2.7输入以下指令 python -m pip install --upgrade pip 2.如果是python 3.+输入以下指令 python3 -m pip instal ...

  4. day1-Python擅长的领域+学习内容

    Python擅长的领域 WEB开发 Django   Pyramid     Tornado       Bottle    Flask    WebPy 网络编程 Twisted        Re ...

  5. pointer-events属性值详解

    其实早知道这个属性,但是一直没有去研究过.今天正好在twitter看到这个词,就去研究了下,正好解决了目前遇到的一个小难题,所以分享下.嗯,其实这是个比较简单的CSS3属性. 在某个项目中,很多元素需 ...

  6. 纯CSS让overflow:auto页面滚动条出现时不跳动

    现代浏览器滚动条默认是overflow:auto类型的,也就是如果尺寸不足一屏,没有滚动条:超出,出现滚动条.于是,问题来了: 信息流页面,如新浪微博,是从上往下push渲染的.开始只有头部一些信息加 ...

  7. 用eclipse查看JDK源代码

    把jdk的源代码导入eclipse

  8. SpringMVC Controller层的单元测试

    Getting Ready 测试相关Maven dependency如下: <dependency> <groupId>org.springframework</grou ...

  9. 转载:怎样用通俗的语言解释REST,以及RESTful?

    作者:覃超链接:https://www.zhihu.com/question/28557115/answer/48094438来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...

  10. java 并发(五)---AbstractQueuedSynchronizer(4)

    问题 : rwl 的底层实现是什么,应用场景是什么 读写锁 ReentrantReadWriteLock 首先我们来了解一下 ReentrantReadWriteLock 的作用是什么?和 Reent ...