JS(五):面向对象之闭包,声明,遍历,存储,封装,继承,关键字,对象冒充

一.对象的概述

(一)面向对象概述

1.面向对象:对代码的一种抽象,对外统一提供调用接口的编程思想

2.基于原型的面向对象:对象(object)是依靠构造器(constructor)利用原型(prototype)构造出来的。

3.js面向对象的名词解释:
属性:事物的特性,自身拥有的东西。(例如人拥有的姓名,年龄,身高)
方法:事物的功能。(例如人 学习,玩,唱歌)
对象:事物中的一个实例。(例如众多人中的一个)
原型:js函数中由prototype属性引用了一个对象,即原型对象(原型)。

function F(){//自定义函数

}
alert(F.prototype);//对象     F.prototype---->内存地址---->存储一个对象
alert(F.prototype instanceof Object);//true

所有的函数都有一个prototype对象。

4.构造函数对象:函数构造器用来创建函数对象。

var obj = new Function(arg1,arg2,...,functionBody());//Function内的所有参数必须加引号,包括变量和函数。

arg1,arg2…为正常变量,相当于functionBody内的参数,functionBody()为自定义函数体。

注意:构造器构造的对象,效率低,arg1,arg2…顺序在functionBody中不能变。

var obj = new Function("a","b","return a+b");
var s=obj(2,5);
alert(s);

5.对象分为函数对象和普通对象。
通过new Function()方式创建的对象都是函数对象,其他对象都是普通对象。

(二)闭包

1.闭包:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数)

变量作用域分为两种:全局变量、局部变量。
js中,函数内部可以读取全局变量。函数外无法访问到函数内的局部变量

解决办法:在函数a内再定义一个函数b,函数b就能访问到函数a内的局部变量。

function a(){
	var i=0;
	function b(){
		alert(++i);
	}
	return b;
}
var c=a();
c();//1 相当于b()

这里例子中,c其实指向的函数b,c()其实就是b()。

2.闭包的特点:函数b是在a内嵌套的,函数a需要返回函数b。
3.闭包的用途:
a.读取函数内部变量。
b.让i变量的值保留在内存中。
4.闭包的优缺点(面试常问)
优点:当闭包函数作为另一个函数调用时,有利于封装;可以访问局部变量。
缺点:以为变量一直在内存中,所以内存占用浪费严重;内存泄漏(黑客的攻击手段主要就是攻击内存) 所以闭包要谨慎使用。

例题1:

var a=111;
function test(){
	alert(a);
	var a=11;
	function subTest(){
		alert(a);
	}
	return subTest;
}
test()();

依次弹出:undefined 11
解析:
test()(): 先执行test, 再执行subTest
test内部重新声明var变量a,预解析时还未执行赋值,test内alert(a)是局部变量

例题2:

function test(){
	var n=10;
	function subTest(){
		n++;
		alert(n);
	}
	return subTest;
}
var func=test();
func();
func();

依次弹出:11 12
该题考察的是闭包,闭包不止可以取到外层函数中的局部变量,还可以将值保存在内存中。 本题分析可参考如下:
1、var func=test()执行之后func指向subTest方法,func()第一次调用,弹出n的值是11,因为++在后,的确是先参与运算,在进行自加,可在这里没有表达式,也没有运算,自接就是进行了n++;下面alert就是下一步操作了,肯定是需要上部操作执行完的,所以是自加之后的值所以是1。
2、接下来就是闭包的作用了,闭包有2个作用:一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。所以第一次调用之后,n的值变为11,在第二次调用的时候,访问到的n是11,++之后,就是12了。
0、预解析定义函数,声明变量,赋值undefined
1、var func=test(); 执行函数test,n赋值10,返回函数体subTest 赋值给func;
2、func(); 第1次执行函数subTest ,++在后,的确是先参与运算,在进行自加,可在这里没有表达式,也没有运算,自接就是进行了n++;
3、func(); 第2次执行函数subTest ,

二.对象声明方式

Object使所有对象的基类、根,所有的js对象都是由Object延伸的。

1.字面式对象声明(json格式)

var person = {
	name:"zhangsan",
	age:26,
	sex:"man",
	eat:function(fds){
		alert("我在吃"+fds);
	},
	play:function(ga){
		alert("我在玩"+ga);
	}
}
//alert(person.age);
//person.eat("面条");
alert(person instanceof Object);

2.new操作符后跟Object构造函数(Object方式)

var box = new Object();
box.name = "zhangsan";
box.age = 100;
box.infos = function(str){
	return this.name+"---"+this.age+"---"+str; //this 当前对象
}
//alert(box.name);
var con = box.infos("吃饭那");
alert(con);

3.js中构造方式声明对象(构造函数方式)

function person(name,sex,age){
	this.name = name; //this.name是属性  name是参数 		习惯上属性名称和参数名称一样
	this.sex = sex;
	this.age = age;
	this.show = function(){
		alert(this.name+"---"+this.sex+"---"+this.age);
	}
}
var obj1 = new person("zhangsan","nan",18);//实例化
//alert(obj1.sex);
obj1.show();
var obj2 = new person("lisi","nv",20);
obj2.show();

注意: this 代表当前对象,obj1和obj2两者之间是独立的,函数内部只能用this访问属性和方法

4.js中工厂方式声明对象
工厂模式:按照某种模式,可以不断的创造对象。(实质就是自定义一个函数,在函数中用Object()方式,创建对象,设置属性、方法,并返回对象,然后每调用一次函数,就创建一个对象,函数的参数可设置)。

面向对象的核心:无论以何种方式创建的对象,彼此之间都是无关联的。

function createObject(name,age){
	var obj = new Object();
	obj.name = name;// obj.name 属性 name参数
	obj.age = age;
	obj.run = function(){ //在obj对象中 调用obj对象的属性 this 代表的当前对象
		return this.name +"----" + this.age +"运行中....";
	}
	obj.say = function(){
		return "今天天气不错";
	}
	return obj;
}
var box1 = createObject("张三",18);
//alert(box1.name); //调用属性成功
//alert(box1.run()); //调用方法成功
var box2 = createObject("李四",20);//box1 和 box2没有任何关系
//alert(box2.name);
alert(box2.run());

运行过程:由上到下;function createObject()载入内存,准备调用;赋值给参数name=“张三”,age=18;参数赋值给属性;返回的obj给了box1;

任何模式下,同种模式中的创造出来的对象都是独立存在的

构造和 工厂模式区别:
a.构造方式不会显示创建对象 将属性赋值给 this ,不需要return 对象
b.工厂 在方法内部创建 object对象 返回object对象 ,属性和方法都是赋给object对象.

5.原型模式

任何js方法或函数,都自带一个prototype属性,且它以对象方式存在。

原型模式根本:函数本身声明为空内容,利用prototype定义一些属性及方法。好处是:让所有实例化的对象都拥有它包含的属性及方法。

function test(){//任何方都自带一个prototype

}
//alert(test.prototype instanceof Object); //true  自带该对象 prototype是Object子对象
test.prototype.color = "red";
test.prototype.heights = "1.7";
test.prototype.widths = "1.2";
test.prototype.showInfo = function(){
	alert(this.color+"---"+this.heights+"---"+this.widths);
}
test.prototype.getinfo = function(){
alert("aaaaa");
}
var car1 = new test();
car1.getinfo();//调用成功

原型模式另一种写法(json数据定义属性和方法):

function test(){

}
//json数据定义属性和方法
test.prototype={
	color:"red",
	heights:"1.7",
	widths:"1.2",
	showinfo:function(){
		alert(this.color+"---"+this.heights+"---"+this.widths);
	},
	getinfo:function(str){
		alert(str);
	}
}
var car1 = new test();
car1.getinfo("abc");//调用成功

6.混合模式:构造模式+原型模式

function blog(name,url,friend){
	this.name = name;
	this.url = url;
	this.friend = friend;
}
blog.prototype={
	test:"awt",
	showinfo:function(){
		alert(this.name+"---"+this.url);
	},
	gets:function(){
		alert(this.friend);
	}
}
var peo = new blog("张三","http://www.baidu.com","李四");
peo.showinfo();

混合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性,但同时又共享着方法,最大限度的节省了内存。这种模式在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。

原型模式可以让所有对象实例共享它所包含的属性及方法。它省略了构造函数传递初始化参数这一环节,但是会导致所有实例在默认情况下都取得了相同的属性值,这样非常不方便,另, 原型模式的最大问题在于共享的本性所导致的,由于共享,因此当一个实例修改了引用的值,另一个示例也随之更改了引用。

工厂模式是在函数内声明一个对象,给这个对象添加属性和方法,最后通过return将这个对象的引用暴露出去,这样调用函数之后就可以拿到对象的引用,访问对象的属性和方法, 工厂模式解决了创建多个相似对象的问题,但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,

单一使用一种模式各有优缺点, 推荐使用混合模式, 不过以后也可以根据实际需求, 灵活使用其中的让任意一种方式。

三.对象遍历及存储

(一)对象的遍历

遍历对象的属性,对象可以当作数组去处理,for in。

/*
var ren1 = {};
ren1.name = "zhangsan";
ren1.age = "18";
ren1.len = "180";
ren1.demo = function(){
	alert(this.name);
}
*/
function ren(){
	this.name="zhangsan";
	this.age = "18";
	this.leng="180";
	this.demo = function(){
		alert(this.name);
	}
}
//alert(ren.age);
var r = new ren();//必须实例化才能遍历
for(var i in r){ //i是属性名称或方法名称
	alert(r[i]); //取得是属性的值 或者是方法的定义代码
}

for(var i in ren )在遍历对象时,i的取值是对象的所有属性名称和方法名称。
使用构造函数声明的对象要实例化后才可以进行遍历。

(二)对象的存储

对象是如何存储的

function Animal(){
	this.name="Tom";
	this.hobby = function(){
		console.log("爱抓老鼠");
	}
}
var cat =new Animal();
var kitty = cat;//对象赋值,引用传递,给new Animal()创建的对象赋值一个新的名称。
var dog =new Animal(); //第二个对象。

a.内存空间分为栈内存,堆内存,代码段,数据段
b.cat 和 kitty是引用的同一个地址,在栈内存内指针指向堆内存。
c.Animal下面的属性名和普通类型值都存在堆内存,有方法的属性名值为一个地址引用在数据段的方法,
所以方法存在代码段里面的。
d.dog和cat不是同一个对象,new了一个新对象出来,互不相干。

四.封装

封装(Encapsulation):把对象内部数据和操作细节进行隐藏。

大多数面向对象语言都支持封装的特性,提供了private关键字来隐藏某些属性或方法,用来限制被封装的数据或者内容的访问,只对外提供一个对象专门访问的接口。这个接口一般是一个方法。

js中可以通过闭包实现封装。函数内部声明的变量,外部是访问不到的。能否再对象外部被访问,就是公有与私有内容的区别。
典型的闭包使用方法:

封装:

function A(){
	var t = 3;
	function _xx(){ alert(11+"****") ;}
	this.xx = function(){
		return _xx;
	}
}
A.prototype = {
	oth:function(){
		alert("普通方法");
	}
}
var a = new A();
var b = a.xx(); // a.xx() ---> function _xx()
//b();
a.oth();

缺陷: 1 占用内存 2 不利于继承

五.继承

(一)原型继承

原型:是利用prototype对象添加属性和方法。
原型链:js在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。

同名的子方法、属性会覆盖父方法、属性。

// js 继承:
//原型:用prototype对象来添加属性和方法

var person = function (){
	__proto__

};
var p = new person(); //三个阶段 p.__proto__
// 1 var p = {}; 创建对象
// 2 p.__proto__ = person.prototype __proto__ 自带的一个属性 ***
// 3 创建对象(初始化对象)p --> person.call(p)
//alert(p.__proto__ instanceof Object);
alert(p.__proto__ == person.prototype)

//如果p.name 不存在 --》 __proto__去查找相当于p.__proto__对象 -->p.__proto__.__proto__....这就是原型链

每一个对象都有__proto__,p.proto__本身是一个对象,它也有相应的__proto。所以当p.name属性不存在时,它会去p.__proto__中查找,p.__proto__中没有,就会再往p.proto.__proto__中查找,依次下去,这就是一个原型链。

js 继承:

var person = function(){}
	person.prototype.say = function(){
		alert("天气挺好");
	}
var p = new person();
p.say(); // p 没有say方法的,然后去找p.__proto__ 里找, p.__proto__相当于 person.prototype, person.prototype有say方法

// p.__proto__ = person.prototype ---》 有say方法

// js 继承:

var person = function(){};//父
person.prototype.say = function(){
	alert("天气挺好");
}
person.prototype.gongzi = 500;

var programmer = function(){};//子
programmer.prototype = new person();
programmer.prototype.wcd = function(){
	alert("明天天气也不错");
}
programmer.prototype.gongzi=1000;


var p = new programmer();/p.say(); //可以调用
p.wcd(); //可以调用
alert(p.gongzi);//1000
//原型链实现过程
// var p = new programmer(); p.__proto__ = programmer.prototype = new person();
//var p1 = new person(); programmer.prototype = p1
// p.say(); p.__proto__ --> programmer.prototype ==p1 --->p1.__proto__ ==person.prototype.say();

原型继承:用到原型链的概念

function person(name,age){//父
	this.name= name;
	this.age = age;
	}
person.prototype.sayhello = function(){
	alert("属性name值"+this.name);
}
function student(){} ;//子
student.prototype = new person("李四",18);// 原型继承
student.prototype.grade = 3;
student.prototype.test = function(){
	alert(this.grade);
}
var s = new student();
s.sayhello();
alert(s.grade);
//过程分析:
// s.__proto__ = student.prototype = p1 p1.__proto__ = person.protype.sayhello();

首先要明确一点,原型链是指对象的原型链,所以原型链上的所有节点都是对象,不能是字符串、数字、布尔值等原始类型。

另外,规范要求原型链必须是有限长度的(从任一节点出发,经过有限步骤后必须到达一个终点。显然也不能有环。)

那么,应该用什么对象作为终点呢?很显然应该用一个特殊的对象。

好吧,Object.prototype确实是个特殊对象,我们先假设用它做终点。那么考虑一下,当你取它的原型时应该怎么办?即

Object.prototype.proto;
应该返回什么?
取一个对象的属性时,可能发生三种情况:

如果属性存在,那么返回属性的值。

如果属性不存在,那么返回undefined。

不管属性存在还是不存在,有可能抛异常。

我们已经假设Object.prototype是终点了,所以看起来不能是情况1。另外,抛出异常也不是好的设计,所以也不是情况3。那么情况2呢,它不存在原型属性,返回undefined怎么样?也不好,因为返回undefined一种解释是原型不存在,但是也相当于原型就是undefined。这样,在原型链上就会存在一个非对象的值。

所以,最佳选择就是null。一方面,你没法访问null的属性,所以起到了终止原型链的作用;另一方面,null在某种意义上也是一种对象,即空对象,因为null一开始就是为表示一个“空”的对象存在的。这样一来,就不会违反“原型链上只能有对象”的约定。
所以,“原型链的终点是null”虽然不是必须不可的,但是却是最合理的。

(二)构造继承

构造函数继承:在子类内部构造父类的对象实现继承。
对象内置方法中apply和call都可以用于继承,区别在于传参方式不同。
//构造函数(this)继承:

function parents(name){
	this.name = name;
	this.say = function(){
		alert("父亲的名字:"+this.name);
	}
}
function child(name,age){ //继承parents
	this.pObj = parents;//用父对象来创建子对象
	this.pObj(name);
	this.age = age;
	this.sayC = function(){
		alert("child:"+this.name+"---"+"age:"+this.age);
	}
}

var p = new parents("zhangsan");
p.say();//父亲的名字:zhangsan
var c = new child("李四",20);
c.sayC();//child:李四---age:20
// 李四---》 this.pObj(name); ---》 parents(name) ---> this.name=name="李四"
// this.sayC --->this.name来源于--->parents-->this.name

父对象 被子对象继承 所有的属性和方法,都将传递到子对象中*****

call 和apply的用法:
call:调用一个对象的一个方法,以另一个对象替换当前对象
apply:应用某一个对象的一个方法,用另一个对象替换当前对象
call—>obj.call(方法,var1,var2,…)
apply—>obj.call(方法,[var1,var2,…])

function person(name,age,len){
	this.name = name;
	this.age = age;
	this.len = len;
	this.say = function(){
		alert(this.name+":"+this.age+":"+this.len);
	}
}
//call继承
function student(name,age){
	person.call(this,name,age);
}
//apply继承
function teacher(name,age,len){
	person.apply(this,[name,age,len])
}

var per = new person("张三",25,"170");
per.say();
var stu = new student("李四",18);
stu.say(); // 李四 18 undefined
var tea = new teacher("王武",20,"180");
tea.say();

在这里插入图片描述

六.关键字

js面向对象的关键字:instanceof delete call apply arguments callee this

1.instanceof :判断变量是否是对象的实例

var arr = new Array();
alert(arr instanceof Array);//true  是Array的实例
alert(arr instanceof Object);//true  Array继承于Object,所以也是它的实例

function test(){}
var obj = new test();
alert(obj instanceof test);//true

alert(obj instanceof Object);//true

所有对象本质上都是继承于Object。

2.delete: 删除对象的属性

delete用于删除对象的属性,无法删除对象的方法,也删除不了变量,也删除不了原型链中的属性和变量。

function fun(){
	this.name = "zhangsan";
	this.say = function(){
		alert(this.name);
	}
}
var obj = new fun();
/*
alert(obj.name);//zhangsan
delete obj.name; //删除name属性
alert(obj.name);//undefined
*/

obj.say();//zhangsan
delete obj.say(); 
obj.say();//zhangsan
//delete用于删除对象的属性,对方法不起作用

var demo = "lisi";
alert(demo);
delete demo; //删除不了变量
alert(demo);

3.call和 apply方法

/*
function add(a,b){
	alert(a+b);
}
function subs(a,b){
	alert(a-b);
}
//add.call(subs,5,3); // subs-->add ===>add(5,3) subs只能引用一个存在的对象
add.apply(subs,[5,3]);
*/
function animal(){
	this.name = "ani";
	this.showName = function(){
		alert(this.name);
	}
}
function cat(){
	this.name = "cat";
}
var an = new animal();
var c = new cat();
//an.showName.call(c,","); // 通过call方法,将showName--》cat使用了
an.showName.apply(c,[]);

4.callee:返回正在执行的function对象,也就是指向function对象的内容
用法:callee是arguments的一个属性,arguments.callee,这个属性的默认值就是正在执行的function对象。callee指代函数本身。

function demo(){
	alert(argument.callee);//callee当作属性,返回的是函数内容
	alert(arguments.callee());//callee是一个属性,不能当作方法。会报错,因为陷入死循环
}
demo();

arguments存在于所有函数中,即所有函数都有arguments。

alert(arguments.callee);//callee当作属性,返回的是函数内容
alert(arguments.callee());//callee是一个属性,不能当作方法。报错,因为陷入死循环

如果给一个终止条件,可以让它从死循环中跳出来。所以有时可以用arguments.callee()来替代递归函数。
注意用arguments.callee()时一定要加终止条件,不然会陷入死循环。如下代码

var sum=function(n){
	if(n<=1){
		return 1;
	}else{
		return n+arguments.callee(n-1);//在函数内部调用本函数
	}
}
alert(sum(5));

5.arguments:每个函数都有一个Arguments对象的实例arguments,它引用函数的参数(实参)
可以用数组下标的方式引用arguments元素。
arguments.length:参数的个数。
arguments.callee:引用函数自身。

function test(a,b,c){
	alert(arguments.length);
	alert(arguments[0]);//arguments如果表示参数,是可以遍历的
	alert(arguments.callee);
}
test(a,b,c);

6.this:
(1).可以在函数内部定义属性/变量

function test(){
	this.x = 1; //this全局变量 global == 》 x=1
	alert(this.x);
}
test();
//等价于:
var x = 1;
function test(){
this.x = 0 //改变了 全局变量 x 值
}
test();
alert(x);

(2).作为方法调用:构造函数内 this指当前对象

function test(){
	this.name = "zhangsan"; //this表示当前对象 --》 t
	this.age = 18;
}
var t = new test();
alert(t.name);

(3).在call apply: this第一个参数

var x = 0;
function test(){
	alert(this.x)
}
var o = {};
o.x = 1;
o.m = test
//o.m.apply(); // 0
o.m.apply(o); //1;

七.对象冒充

对象冒充:将父类的属性和方法一起传给子类作为特权属性和特权方法。
特权方法和属性就是在闭包中可以被访问到的属性(变量)与方法(函数)。
只是继承父类的特权属性和方法,但是父类的原型当中的属性/方法没有继承。
// 对象冒充

function person(name,age){
	this.name = name;//特权属性和特权方法
	this.age = age;
	this.sayHi = function(){
		alert("hi");
	}
}
person.prototype.walk = function(){
	alert("walk......");
}
function student(name,age,grade){
	this.newMethod = person; //冒充person对象,传递特权属性和特权方法给子类
	this.newMethod(name,age)
	this.grade = grade;
}
var s1 = new student("zhangsan",15,5); //s1 是student 对象 ,继承person,拥有person所有属性和方法
s1.walk();//报错,walk属于person.prototype,不属于person
s1.sayHi();//hi

注意 :s1继承了 person中的特权方法和属性,没有继承共有方法和属性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值