一、面向对象的概述
ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。
面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。
但是,ECMAScript 没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
js是基于对象,不是面向对象的。不具备描述事物的能力。
1、创建一个对象,然后给这个对象新建属性和方法
var box = new Object(); //创建一个 Object 对象 box.name = ‘Lee‘; //创建一个 name 属性并赋值 box.age = 100; //创建一个 age 属性并赋值 box.run = function () { //创建一个 run()方法并返回值 return this.name + this.age + ‘运行中...‘; }; alert(box.run()); //输出属性和方法的值
上面创建了一个对象,并且创建属性和方法,在 run()方法里的 this,就是代表 box 对象本身。
这种是 JavaScript 创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。
var box = new Object(); //创建一个 Object 对象 box.name = ‘Lee‘; //创建一个 name 属性并赋值 box.age = 100; //创建一个 age 属性并赋值 box.run = function () { //创建一个 run()方法并返回值 return this.name + this.age + ‘运行中...‘; }; alert(box.run()); //输出属性和方法的值 var box2 = box; //得到 box 的引用 box2.name = ‘Jack‘; //直接改变了 name 属性 alert(box2.run()); //用 box.run()发现 name 也改变了 var box2 = new Object(); box2.name = ‘Jack‘; box2.age = 200; box2.run = function () { return this.name + this.age + ‘运行中...‘; }; alert(box2.run()); //这样才避免和 box 混淆,从而保持独立
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
function createObject(name, age) { //集中实例化的函数 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + ‘运行中...‘; }; return obj; } var box1 = createObject(‘Lee‘, 100); //第一个实例 var box2 = createObject(‘Jack‘, 200); //第二个实例 alert(box1.run()); alert(box2.run()); //保持独立
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
function createObject(name, age) { //集中实例化的函数 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + ‘运行中...‘; }; return obj; } var box1 = createObject(‘Lee‘, 100); //第一个实例 var box2 = createObject(‘Jack‘, 200); //第二个实例 alert(box1.run()); alert(box2.run()); //保持独立 alert(typeof box1); //Object //alert(box1 instanceof box);//不能判断 alert(box1 instanceof Object); //true 都是Object类型 无法区分谁到底是谁的
2、第二种创建对象的方式:
ECMAScript 中可以采用构造函数(构造方法)可用来创建特定的对象。类型于 Object 对象(用函数来模拟面对对象的中的描述)。
//用js来描述人 function Person(){ //相当于构造器。 alert("person run"); } //通过描述进行对象的建立。 new. var p = new Person(); //动态给p对象添加属性。直接使用p.属性名即可。 p.name = "zhangsan"; p.age = 29; //如果定义的p对象的属性赋值为一个函数,即是给p对象添加一个方法。 p.show = function(){ alert("show :"+this.name+":"+this.age); } p.show();
function Box(name,age){ this.name = name; this.age = age; this.run = function(){ return this.name+this.age+"运行中....."; } } var box1 = new Box(‘Lee‘, 100); //new Box()即可 var box2 = new Box(‘Jack‘, 200); alert(box1.run()); alert(box1 instanceof Box); //很清晰的识别他从属于 Box
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有 new Object(),为什么可以实例化 Box(),这个是哪里来的呢
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());但它后台会自动var object = new Object();
2.直接将属性和方法赋值给 this 对象;this就相当于object
3.没有 renturn 语句,构造函数不需要返回对象的引用,它是后台自动返回的
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);
2.通过构造函数创建对象,必须使用 new 运算符。
既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方执行了?执行的过程如下:
1.当使用了构造函数,并且 new 构造函数(),那么就后台执行了 new Object();
2.将构造函数的作用域给新对象,(即 new Object()创建出的对象),而函数体内的 this 就代表 new Object()出来的对象。
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。
关于 this 的使用,this 其实就是代表当前作用域对象的引用。如果在全局范围 this 就代表 window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
var box = 2; alert(this.box); //全局,代表 window
构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用 new 运算符来调用,否则就是普通函数(函数名首字母无序大写)。
function Box(name, age) { //构造函数模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + ‘运行中...‘; }; } var box = new Box(‘Lee‘, 100); //构造模式调用 alert(box.run()); // Box(‘Lee‘, 20); //普通模式调用,无效
可以使用对象冒充来调用
function Box(name, age) { //构造函数模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + ‘运行中...‘; }; } var o = new Object(); Box.call(o, ‘Jack‘, 200) //对象冒充调用,o这个对象冒充Box对象来运行 alert(o.run());
探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。
function Box(name, age) { //构造函数模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + ‘运行中...‘; }; } var box1 = new Box(‘Lee‘, 100); //传递一致 var box2 = new Box(‘Lee‘, 100); //同上 alert(box1.name == box2.name); //true,属性的值相等 alert(box1.run == box2.run); //false,方法其实也是一种引用地址 alert(box1.run() == box2.run()); //true,方法的值相等,因为传参一致
可以把构造函数里的方法(或函数)用 new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性。
function Box(name, age) { //new Function()唯一性 this.name = name; this.age = age; this.run = new Function("return this.name + this.age + ‘运行中...‘"); } var box1 = new Box(‘Lee‘, 100); //传递一致 var box2 = new Box(‘Lee‘, 100); //同上 alert(box1.name == box2.name); //true,属性的值相等 alert(box1.run == box2.run); //false,方法其实也是一种引用地址 alert(box1.run() == box2.run()); //true,方法的值相等,因为传参一致
我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,但这种做法没什么必要,只是加深学习了解:
function Box(name, age) { this.name = name; this.age = age; this.run = run; } function run() { //通过外面调用,保证引用地址一致 return this.name + this.age + ‘运行中...‘; } var box1 = new Box(‘Lee‘, 100); //传递一致 var box2 = new Box(‘Lee‘, 100); //同上 alert(box1.name == box2.name); //true,属性的值相等 alert(box1.run == box2.run); //true,用的是同一个函数,所以地址相同 alert(box1.run() == box2.run()); //true,方法的值相等,因为传参一致
虽然使用了全局的函数 run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的 this 在对象调用的时候是 Box 本身,而当作普通函数调用的时候,this 又代表 window。
二、对象中的成员:
1、js实现私有成员:(在java中,有成员的访问修饰符的概念:private,默认,prototype,public,但是在js中没有,只能模拟出私有成员,即通过作用域不同来体现,一般不这样用阅读性差)
function Person(name,age){ this.name = name; this.age = age; //这是一个局部变量,如果将var去掉就变成全局变量了 //在js中,通过this把变量和方法与类关联上的,如果自己用var定义了一个变量或者直接定义了一个方法,会认为这个方法或变量与当前的类无关 //可以被看做是局部的变量和方法 var sex = "女"; setSex = function(){ sex = "男"; } //通过this修饰方法 //通过成员方法调用局部方法修改局部变量 this.changeSex = function(){ setSex(); } //通过成员方法返回局部变量的值 this.getSex = function(){ return sex; } } //创建一个对象 var p = new Person("科比",34); //alert(p.name+"****"+p.age); //alert(p.sex);//结果是undefined,不能直接访问, alert(p.getSex());//女 p.changeSex(); alert(p.getSex());
2、给类添加静态的属性和方法
function Person(name,age){ this.name = name; this.age = age; //这是一个局部变量,如果将var去掉就变成全局变量了 //在js中,通过this把变量和方法与类关联上的,如果自己用var定义了一个变量或者直接定义了一个方法,会认为这个方法或变量与当前的类无关 //可以被看做是局部的变量和方法 var sex = "女"; setSex = function(){ sex = "男"; } //通过this修饰方法 //通过成员方法调用局部方法修改局部变量 this.changeSex = function(){ setSex(); } //通过成员方法返回局部变量的值 this.getSex = function(){ return sex; } } //添加静态属性 Person.sex = ‘男‘; //添加静态方法 Person.run = function(){ alert(‘run‘); } //调用静态属性和静态方法 alert(Person.sex); alert(Person.run());