原型与构造函数
一、对象
面向对象的编程语言中有一个非常重要的参数那就是类的概念,通过类我们可以创建出来各种各样的对象,而ECMAscript中没有类的概念,所以他与别的编程语言中处理对象的方式有所不同。
ECMA-262中规定了对象:无数属性的集合,其属性可以包含基本值,对象或其他函数 在 JavaScript 中,对象是拥有属性和方法的数据。 属性是与对象相关的值。 方法是能够在对象上执行的动作。
案例1:
var people=new Object();
people.name="zhangsan";
people.sex=18;
people.say=function(){
alert(people.name+" "+people.sex)
}
people.say();
我们还可以这么写
var people={
name:"zhangsan",
age:18,
say:function(){
alert(this.name)
}
}
people.say();
delete people.name----注意点1:我们可以使用delete删除对象的某个属性
举例生活中的对象 先回顾一下我们的对象:
function people(name,age){
this.name=name; ---在类中通过this关键字添加的属性输入共有 //那私有的呢?
this.age=age;
this.changeName=function(newName){
this.name=newName;
}
}
var zhangsan=new people("zhangsan",18);
zhangsan.changeName("lisi");
alert(zhangsan.name+" "+zhangsan.age)
当您像这样声明一个 JavaScript 变量时: var txt = "hello world"; 其实已经声明了一个字符串对象(想想为什么这么说) 字符串对象拥有内建的属性 length。对于上面的字符串来说,length 的值是 5。字符串对象同时拥有若干个内建的方法。
其实:
JavaScript 中的所有事物都是对象:字符串、数字、数组、日期,等等。对象基础部分结束。
1、创建对象部分 设计模式
(1):工厂模式
工厂模式抽象了创建具体对象的过程,考虑到ECMA无法创建类开发人员发明了一种函数,用函数封装了创建对象的细节。
演示代码:
function createPeople(name,age,sex){
var o=new Object();
o.name=name;
o.sex=sex;
o.age=age;
o.say=function(){
alert(this.name)
}
return o;
}
var zhangsan=createPeople("zhangsan",18,'male');
alert(zhangsan.name)
(2):构造函数模式
什么是构造函数?---用于构造一个对象出来的函数 例如Object 和数组等等。
演示代码:
function People(name,age,sex){
this.name=name;
this,age=age;
this.sex=sex;
this.say=function(){
alert(this.name)
}
}
var zhangsan=new People("zhangsan",18,'male')
zhangsan.say();
在这个例子中和工厂模式的区别?直接将属性和方法赋值给了this对象,没有了return语句 语法注意,构造函数开头字母必须大写---想一想我们还学过哪些构造函数的案例。
注意:
每个通过构造函数创建出来的对象都有一个constructor属性。
演示代码:
例如
alert(zhangsan.constructor)
alert(zhangsan.constructor==People)
注意构造函数毕竟也是函数,也可以当作参数传递:
1:var zhangsan=new People("zhangsan",18,'male')---当作构造函数使用
2:People("zhangsan",18,'male')---当作普通函数调用,这里对象会被添加给window对象
window.say();
3:注意第三种----友情提示:前方高能
var o=new Object();
People.call(o,"zhansgan",18,'male')---这玩意眼熟嘛
o.say()
补充1:
http://uule.iteye.com/blog/1158829---引入网址
1、call方法:
语法:xxx.call([thisObj[,arg1[, arg2[, [,.argN]]]]]) arguments
定义:调用一个对象的call方法,以另一个对象替换当前对象。
说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
演示代码:
function add(a,b)
{
alert(a+b);
}
function sub(a,b)
{
alert(a-b);
}
add.call(sub,3,1);
案例:人 学生 老师 对象继承 方法覆盖
function People(name,age){
this.name=name;
this.age=age;
this.say=function(){
alert(this.name+" "+this.age);
}
}
function Student(sName,sAge){
//调用一个对象的call方法,以另一个对象替换当前对象。
People.call(this,sName,sAge);//发生了继承----伪装继承
}
var zhangsan=new Student("zhangsan",18);
zhangsan.say();
function Teacher(name,age){
People.call(this,name,age);
}
var laowang=new Teacher("laowang",43);
laowang.say();
2、apply方法
语法:apply([thisObj[,argArray]]) 定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明: 如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
演示代码:
function Animal(){
this.name = "Animal";
this.showName = function(){
alert(this.name);
}
}
function Cat(){
this.name = "Cat";
}
var animal = new Animal();
var cat = new Cat();
//通过call或apply方法,将原本属于Animal对象的showName()方法交给对象cat来使用了。
//输入结果为"Cat"
//animal.showName.call(cat,",");
animal.showName.apply(cat,[]);
实现继承
function Animal(name){
this.name = name;
this.showName = function(){
alert(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
var cat = new Cat("Black Cat");
cat.showName();
构造函数的问题:
关键在方法上:
function People(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
this.say=new Function("alert(this.name)")----想一想方法是不是也是这样调用了别人的函数呢?谁的?
}
var zhangsan=new People("zhangsan",18,"male");
zhangsan.say();
但是----
var zhangsan=new People("zhangsan",18,"male");
var lisi=new People("lisi",18,"male");
alert(zhangsan.say===lisi.say)---false
这是为什么呢? 因为每次都new新的方法导致了这个问题 解决方案:
function People(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
this.say=say;
}
function say(){
alert(this.name)
}
var zhangsan=new People("zhangsan",18,"male");
var lisi=new People("lisi",18,"male");
alert(zhangsan.say===lisi.say)
二、var num = 10;//考虑这玩意怎么出来的?
我们已经知道,我们每创建一种对象都有它的原本的类型(每个函数都有一个prototype) 这个属性是一个指针,指向一个对象,而这个对象的的用途是包含可以由特定类型的所有实例共享的属性和方法
--也就是说使用原型对象可以让所有对象实例共享它所包含的属性和方法,也就是说不用在构造函数中定义对象实例的属性,而是丢到它原型对象中去,
演示代码:
function People(){}
// alert(People.prototype)
People.prototype.name="zhangsan";
People.prototype.age=18;
People.prototype.sex="male"
People.prototype.say=function(){
alert(this.name);
}
var p1=new People();
p1.say();
var p2=new People();
p2.say();
alert(p1.say==p2.say);
1:无论什么时候只要创建了一个新的函数,就会有一组特定的规则为该函数去创建一个prototype属性,这个属性都指向的是原型对象 而所有的原型对象都会默认获得一个constructor属性,这个属性包含一个指向prototype属性所在的函数指针 People.prototype.constructor指向People---通过这个构造函数可以为原型对象添加其他的属性和方法。
案例:
function People(){}
People.prototype.name="zhangsan";
People.prototype.age=18;
People.prototype.sex="male"
People.prototype.say=function(){
alert(this.name);
}
var p1=new People();
p1.name="xiaozuo";
alert(p1.name)-----注意这里打印出来的值是指我们给定的值,覆盖了原型中的值,但是并没有改变它
var p2=new People();
alert(p2.name)----这里打印出来的是原型中的值
注意检测 原型是否属于对象
注意我们在打印p1的时候,在打印的过程中因为已经找到了name这个属性,也就是xiaozuo这个值,所以不会再向上找值(最终一直找到原型当中去) 但是p2没有给他赋值也就是说p2本质上来说没有name这个属性的,所以他必须找到一个值并且打印出来,所以一直会找到原型当中去。
所以这里的根本原因是当为对象的实例添加了一个属性的时候,这个属性就会屏蔽原型对象中的同名属性,换个角度来说,也就是阻止去找这个默认的属性 哪怕你赋值为null也会去找 People.prototype.name=null ;
同样的,当我们给这个属性赋值为null的时候会去找上方的东西嘛? 注意修改两个地方:
People.prototype.name="zhangsan" ;
var p1=new People();
p1.name=null;
alert(p1.name)
打印结果为null 也就是说我们新覆盖的值即使为null他也会组织去寻找那个原型中的值。
那怎么取消这个覆盖呢 我们之前学过一个delete的东西
delete p1.name
alert(p1.name)
重要函数hasOwnProperty()---检测一个属性是存在于原型还是实例中
var p1=new People();
p1.name="zhangsan";
alert(p1.hasOwnProperty("name"))----true因为我们为p1给了一个name的属性值
var p1=new People();
//p1.name="zhangsan";
alert(p1.hasOwnProperty("name"))--false
1、in关键字
使用in关键字检测对象是否拥有某个属性
var p1=new People();
alert(p1.hasOwnProperty("name"))--false我们并没有为p1赋值name属性,但是他会从原型中找到这个值
alert("name" in p1)---true name属性从原型当中而来
枚举取得对象所有的属性:
var key=Object.keys(People.prototype)
alert(key)
注意点
var key=Object.keys(People.prototype)
alert(key)
var p1=new People();
p1.name="zhangsan";
p1.age=13;
var keys=Object.keys(p1)
alert(keys)---这里取得了几个?
2、原型简写
问题:,上面案例中使用原型是不是有点麻烦呢?每一个属性都需要People.prototype.age=18; 这么的方式去写,那有没有简单一点的办法呢?
function People(){}
People.prototype={
name:"zhangsan",
age:18,
gender:'male',
say:function(){
alert(this.name)
}
}
在这里我们将People的prototype设置为等于一个以对象字面量形式创建的新对象,和上面的结果是一样的 但是,我们看这样一个东西:
function People(){}
People.prototype={
name:"zhangsan",
age:18,
gender:'male',
say:function(){
alert(this.name)
}
}
var zhangsan=new People();
alert(zhangsan instanceof Object)
alert(zhangsan instanceof People)
alert(zhangsan.constructor==People) // 打印结果为false
alert(zhangsan.constructor==Object)
原因,我们这次的写法和之前有所不同,每创建一个函数,就会创建它的prototype对象,这个对象会自动获得constructor属性,而我们这次的写法,实际上完全覆盖了默认的prototype对象,因此constructor也指向了别的东西(Object):
如果constructor非常重要,则可以这样:
function People(){}
People.prototype={
constructor:People,
name:"zhangsan",
age:18,
gender:'male',
say:function(){
alert(this.name)
}
}
var p=new People();
alert(p.constructor)
演示代码:
function People(){}
People.prototype={//原型模式
name:"zhangsan",
age:18,
gender:'male',
say:function(){
alert(this.name)
}
}
var zhangsan=new People();
alert(zhangsan.constructor)
//function People(name,age,sex){ 构造器模式
// this.name=name;
// this.age=age;
// this.sex=sex;
// this.say=new Function("alert(this.name)")
//}
// var zhangsan=new People("zhangsan",18,"male");
// alert(zhangsan.constructor)
3、原型动态性:
function People(){}
People.prototype.name="zhangsan";
People.prototype.age=18;
People.prototype.sex="male"
People.prototype.say=function(){
alert(this.name);
}
var p1=new People();
p1.say=function(){
alert("hi")
}
p1.say()---注意这里到底执行的是哪个
虽然第二个say方法是在p1对象构造出来之后才改写的,但是依然能用 但是:
function People(){}
var p1=new People();
People.prototype={
name:"zhangsan",
age:18,
gender:'male',
say:function(){
alert(this.name)
}
}
p1.say()---报错了,因为原型是咋创建了对象之后才改写的
当然除了我们自定义的对象有原型外,原生态的对象也是有原型的
三、继承
关于继承我们之前已经讲过一种方式了call继承 那么继承到底有什么好处呢?继承是OO编程中最重要的思想。
1、原型链
ECMAscript中描述了原型链的概念,并且将原型链作为最主要的继承方式,主要思想是让一个引用对象继承另一个引用对象的属性和方法
注意点:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,那么我们如果让原型对象等于另外一个类型的实例,那么原型对象将包含一个指向另外一个原型指针,相应的另一个原型中也包含这一个指向两一个构造函数的指针,加入另外一个原型优势另一个类型的实力,那么之上的关系将被延续。
使用原型链:
function parentClass() {
this.methoda = function() {
alert("parentClass method");
}
}
function subClass() {
this.methodb = function() {
alert("subClass method");
}
}
subClass.prototype = new parentClass();
var temp = new subClass();
temp.methoda();
temp.methodb();
案例2:
function SuperType(){
this.colors=["red","yellow","green"]
}
function SubType(){}
SubType.prototype=new SuperType();
var v=new SubType();
alert(v.constructor) //注意这里的原型发生了改变
原型链的问题1:
function SuperType(){
this.colors=["red","yellow","green"]
}
function SubType(){}
SubType.prototype=new SuperType();
var v=new SubType();
alert(v.colors )
v.colors.push("hello")
var v2=new SubType();
alert(v2.colors )---注意这里,因为继承,所以所有共享实力都使用了同一个原型,原型中会发生改变
原型问题2:没法向父类构造参数中传递参数, 所以一般比较少的使用原型链。 比较多的使用构造函数继承,最简单的就是之前的call方法了
2、原型式继承
大师级人物道格拉斯提出了种经典的函数:
function object(people){
function F(){}
F.prototype=people;
return new F();
}
在函数类不限创建了一个临时的构造函数,然后将传入的对象作为原型,最后返回临时对象的一个新实力,从本质来讲的话object()对传入其中的对象执行了一次复制
ECMAscript通过新增Object.create()方法来规划了原型继承,这个方法和接受两个参数 1:新对象原型的对象2:新对象定义额外属性的对象(可选) 在传入一个参数的情况下Object.create() 和object()方法是一样的
var people={
name:"zhangsan"
}
var student=Object(people);
student.name="lisi";
alert(people.name)// 注意这里,和原型模式是一样的,类型的值始终会被共享出去
案例2:
var people={
name:"zhangsan"
}
var student=Object(people);
student.name="lisi";
alert(student.name);
var student2=Object(people);
student2.name="wangwu";
alert(student2.name);
在没有必要大规模创建构造函数,仅仅让一个对象和另外一个对性保持类似的情况下,原型继承是完全可以胜任的
关于js中继承有很多中方式,这里就不一一列举了 http://yahaitt.iteye.com/blog/250338----这里是很全面的五种继承方式,可以练习一下。
四、闭包
指有权访问另一个函数作用域中的变量函数。
闭包的定义:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。 要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。
演示代码:
var i=10; //这是什么作用域的?-----这里注意下去再补充一个知识点,函数声明 和函数表达式 作为一次作业,写案例:
function fun(){
alert(i);
}
fun();
这个呢?
function fun(){
var i=10; //作用域
}
alert(i)
再看第三种形式:
function fun(){
i=10; //这个丧心病狂的家伙是什么?
}
fun();
alert(i)
这里其实不加var的时候默认会被声明为全局变量 那如何从外部去得到内部的值呢? 先看这个案例(不要在意这玩意能不能执行),在这个案例中fun2能不能正常访问呢?可以:
function fun1(){
var i=10;
function fun2(){
}
return fun2
}
------高级语法
(function fun1(){
var i=10;
(function fun2(){
alert(i);
})()
})()
匿名函数---传参数
(function(o) {
alert(o);
})('water');
我们这次想办法return了fun2,然后我们发现,可以执行了
function fun(){
var i=10;
function fun2(){
alert(i);
}
return fun2;
}
//alert(fun());
var test=fun();
test();
实验结果:我们可以这样将fun内部的值在外部取出来。
那闭包有什么用呢?
闭包的用途:
最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。 ---缺点 内存消耗
注意:this关键字
var name="hello"
var fun={
name:"zhangsan",
getName:function(){
return function(){---匿名函数的执行有全局性,所以指向了全局name
return this.name
}
}
}
alert(fun.getName()())
使用新的方式,我们先把this对象赋值给了一个名叫this的变量而定义了闭包之后,闭包可以访问到这个变量,所以函数返回后
var name="hello"
var fun={
name:"zhangsan",
getName:function(){
var that=this;
return function(){
return that.name
}
}
}
alert(fun.getName()())