JavaScript原型,原型链 ? 有什么特点? JavaScript如何实现继承?

1、javascript高级程序设计对原型的解释:

1、原型:

我们创建的每个函数(构造函数)都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象),而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法

原型最初只包含constructor属性, 这个属性也是共享的。后边可以自行添加需要的属性和方法。这个属性又指向函数本身(Person)。

按照字面的意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象

使用原型对象的好处是可以让所有的对象实例共享它所包含的属性和方法。

换句话说,就是不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中

2、原型模式创建对象,(其实最好的是共享方法,不共享属性)如下代码:

function Person(){}

Person.prototype.name = "dadaoshenyi";
Person.prototype.age = 26;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName();//"dadaoshenyi"

var person2 = new Person();
person2.sayName();//"dadaoshenyi"

alert(person1.sayName ==person1.sayName);//true,指向堆内存中的相同目标,同一个函数。

函数Person的原型属性(prototype)指向函数的原型对象①(Person.prototype)(显示的这个对象),①这个原型对象包含新添加的属性和方法,还包含一个constructor(构造函数)属性,②这个属性包含一个指向①prototype属性所在函数Person()的指针。

对象实例person1包含了一个内部属性指向__proto__([[Prototype]])(指针)原型对象(Person.prototype)的属性。这就是它与它的构造函数的关系。 原型链。

如上图展示了各个对象之间的关系。

3、理解原型对象:

无论什么时候,只要创建一个函数,就会根据一组特定的规则为函数创建一个prototype属性这个属性指向函数的原型对象

在默认的情况下,所有的原型对象都自动获得一个constructor(构造函数)属性(使用构造函数来创建对象),这个属性包含一个指向prototype属性所在函数的指针。

虽然在所有的实现中都无法访问到[[prototype]], 但是可以通过isPrototypeOf()方法来确认对象之间是否存在这种关系。

如下调用:alert(Person.prototype.isPrototypeOf(person1));//true,测试person1的[[prototype]]指向Person.prototype就返回true

调用Object.getPrototypeOf(person1);可以返回实例对象[[prototype]]的值。

每当代码读取到某个对象的某个属性的时候,都会执行一次搜索,目标是具有给定名字的属性。搜索首先是从对象实例本身开始。如果在实例中找到了这个给定名字的属性,就会返回这个属性的值,如果没有找到,就会继续搜索指针[[prototype]](截图中的__proto__)指向的原型对象,在对象原型中查找具有给定名字的属性。如果找到就返回这个属性的值,如果没找到就会继续向上搜索,沿着指针[[prototype]](截图中的__proto__)指向的原型对象。这正是对象实例共享原型所保存的属性和方法的基本原理。

在对象实例中可以访问保存在原型中的值,但是却不能修改原型中的值,但是可以在实例中重新赋值,如perosn1.name = "changyangzhe";覆盖原有的属性或者方法(就近查找)。

可以使用delete person1.name;删除添加的属性,从而可以继续查找到原型上的属性。

使用hasOwnPrototype("name");方法来检测一个属性到底是属于原型还是实例。

4、原型与in操作符:

有两种使用方式,for...in与in,

单独使用的时候,in操作符会在通过对象能访问到给定属性的时候返回true,无论这个属性是在实例中还是在对象原型中。区别与hasOwnPrototype();方法,查找深度不同。

使用for...in循环来遍历对象中可访问的、可枚举的(enumerated)属性。包括实例中的和原型中的属性,包括在原型中不可枚举属性(将[[Enumerable]]标记为false)的属性,因为根据规定,所有开发人员定义的属性都是可枚举的——只有在IE8及更早的版本中例外。

Object.keys();方法返回所有可枚举***实例属性***的字符串数组。Object.keys(Person.prototype);//"name,age,job,sayName";

想要获取所有的实例属性,无论是否可枚举。使用Object.getOwnPrtotypeNames(Person.prototype);

5、更简单的原型语法:

减少输入、从视觉上更好的封装原型的功能。如下写法:

function Person(){}

Person.prototype = {
      name:"dadaoshenyi",
      age:26,
      job:"Software Engineer",
      sayName:function(){
             alert(this.name);
      }    
};

之前已经知道:每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获取constructor属性。

这里本质上是重写了默认的prototype对象因此constructor属性也就变成了新的对象的constructor属性(指向Object构造函数),不再指向Person函数。

如果需要可以在Person.Prototype中添加属性constructor:Person,就还是原来的样子。

关于constructor属性:这是一个对象,其中包含了函数的arguments、caller、length、name等属性。构造器可原生、可自定义。这种情况下,constructor属性的[[Enumerable]]特性被设为true;原生的情况下是不可枚举的。

6、原型对象的动态性:

由于在原型中查找值的过程是一次搜索,所以我们对原型对象所做的任何修改都是能够立即从实例上反映出来的——即使先创建实例,后修改原型。

如果重写原型对象就会导致现有原型与任何以前已经存在的对象实例之间的联系的切断。

7、原生对象的原型:

原生模式的重要性不仅体现在创建自定义的类型方面,所有原生的引用类型都是采用这种模式创建的,如打印String.Prototype

通过原生对象的原型,可以取得默认方法的引用而且可以定义新的方法,可以向修改自定义对象的原型一样修改原型对象的原型(虽然不推荐)。如下例子:

String.prototype.startsWith = function (text){

      return this.indexOf(text) == 0;

}

var msg = "hello world!";//这里使用var msg = new String("hello world!");也是可以的,应该是使用string类型调用的时候,自动转化为了String包装类型。string与object都可用。

alert(msg.startsWith("hello"));   //true

经典的一个实例:将具有length属性的对象转化为数组。除了IE下的节点组合,IE下对象是以com对象的形式实现的。

Array.prototype.slice.call(arguments);//将类数组转化为数组

var a={length:2,0:'first',1:'second'};
Array.prototype.slice.call(a);//  ["first", "second"]
 
var a={length:2};
Array.prototype.slice.call(a);//  [undefined, undefined]

探究这个方法的实现原理: 

首先,slice有两个用法,一个是String.slice,一个是Array.slice,第一个返回的是字符串,第二个返回的是数组,这里我们看第2个。

var a={length:2,0:'first',1:'second'};
Array.prototype.slice;//function slice() { [native code] }
Array.prototype.slice.call(a);//["first", "second"]

slice() 方法可从已有的数组中返回选定的元素。
语法   arrayObject.slice(start,end)
start    必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
end    可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

转成数组的通用方法:

var toArray = function(s){
      try{
            return Array.prototype.slice.call(s);
      } catch(e){
            var arr = [];
            for(var i = 0,len = s.length; i < len; i++){
                   //arr.push(s[i]);
                   arr[i] = s[i];  //据说这样比push快
             }
             return arr;
      }
}

8、原型对象的问题:

对于创建的对象实例,所有的方法和属性都是共享的,修改也不方便。

9、最常用的创建object模式:(组合使用构造函数模式和原型模式)

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但同时有共享着对方法的引用,最大限度的节省了内存。集两种模式之长。具体的例子如下:

//构造函数模式用于定义实例属性
function Person(name,age,job) {
     this.name = name;
     this.age = age;
     this.job = job;
}

//原型模式用于定义方法和共享的属性
Person.prototype = {
     constructor:Person,
     sayName:function() {
            alert(this.name);
     }
}

10 、如何判断一个对象是否属于某个类? 

1、typeof  形如 var x = "xx";  typeof x == 'string'
    返回类型有:'undefined' “string” 'number' 'boolean'  'function'  'object'   
    缺点:对于object类型不能细分是什么类型 
    优点:对空null的判断 'undefined'的应用
2、instanceof 形如 var d = new String('test'); d instanceof String ==true 
  返回的类型有:String Number Boolean Function Object Array Date
  优点:能区分出更细的类型如 Date Array 如
var num = 3; num instanceof Number 能返回具体的类型
  缺点:直变量不能区分 必须采用new 的对象
3、constructor 形如:var x = []; x.constructor==Array   优点:可以返回继承的类型   缺点: 不能对象的细分,如继承 必须手动修正 4、Object.prototype.toString.call();   优点:通用,返回"[object String]" 具体object的类型   缺点:不能返回继承的类型
var type = function(v){
    return Object.prototype.toString.call(v);
};
console.log(type(null));//[object Null]
console.log(type(undefined));//[object Undefined]
console.log(type(1));//[object Number]
console.log(type(true));//[object Boolean]
console.log(type("2"));//[object String]
console.log(type([1,2,3]));//[object Array]
console.log(type({"name":"zhuhui"}));//[object Object]
console.log(type(type));//[object Function]
console.log(type(new Date()));//[object Date]
console.log(type(/^\d+$/));//[object Regexp] 

2、javascript高级程序设计对原型链的解释:

1、原型链:实现继承

ECMAScript只支持实现继承,而且其实现继承主要依靠原型链来实现。

基本的思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数、原型与原型实例三者的关系:

每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针[[prototype]]

       

让起始的实例的原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,这样层层递进就构成了实例与原型的链条。这就是原型链的基本概念。

也就是说本来实例的内部指针指(__proto__)向自己的原型,自己原型的内部指针(__proto__本来指向于Object对象,现在更改了这个指向,让它指向另外一个对象(这是一个实例,也就是被继承者),显然这个实例也是有一个(__proto__),然后就可以一直传递下去,这就是原型链,这就是继承。

Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

当然是Object.hasOwnProperty(proName);方法了,该方法只会在当前对象上查找是否有需要的属性值,不会通过原型链向上查找。

2、常用的一种基本模式的原型链实现: 

function SuperType(){//类型1的构造函数,以及一个属性
     this.property = true;
}

SuperType.prototype.getSuperValue = function(){//类型1的原型方法
     return this.property;
}

function SubType(){//类型2的构造函数,以及一个属性
     this.subproperty = false; 
}

//继承了SuperType,类型2的原型对象重写为类型1的实例对象。
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){//类型2的添加的一个原型方法
      return this.subproperty;
}

var instance = new SubType();
alert(instance.getSuperValue()); //true

本质上就是,让类型2(SubType)继承了类型1(SuperType),继承是通过创建SuperType的实例,并将其赋值给SubType.prototype实现的

这样,原本属于类型1的实例中的所有属性和方法,现在也存在于类型2的原型中了。然后可以随意在类型2的原型上添加方法了。

 

3、别忘记默认的原型:

所有的引用类型默认都继承了Object,而这个继承也是通过原型链来实现的。

所有的函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。

 

4、确定原型和实例的关系:

两种方法:

方法1、

alert (instance instanceof Object);//true
alert (instance instanceof SuperType);//true

方法2、

alert (Object.prototype.isPrototypeOf(instance));//true
alert (SuperType.prototype.isPrototypeOf(instance));//true

5、谨慎的定义方法:

在子类型中覆盖或者添加新的方法,都要放在替换原型语句之后(重写之后)。

在通过使用原型链实现继承的时候,不能再使用对象字面量来创建原型方法。因为这样会重写原型链。

如这样使用字面量添加信的方法,会导致继承被切断,因为相当于用了一个新的原型代替了之前的继承的原型。

6、原型链的问题:

问题1、就是在使用构造函数结合原型来实现对象创建的时候,方法是原型共享的,属性是是实例共享的。使用原型继承会让SuperType(类型1)的属性成为类型2的原型属性。回到了原型创建对象的问题上了。

问题2、创建子类型的实例的时候不能想超类型的构造函数中传递参数。实际上应该说没有办法在不影响所有实例的情况下,给超类型的构造函数传递参数。

7、常用的继承的方法(最实用):

组合继承:也称伪经典继承,指的是将原型链与借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

原理就是:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例的继承。这样既能在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。

function SuperType(name){//对象类型1的属性
     this.name = name;
     this.colors = ["blue","red","green"];
}

SuperType.prototype.sayName = function(){//对象类型1的方法
     alert(this.name);
}

function SubType(name,age){//对象类型2的属性,其中属性实现了继承,并且可给属性值传递参数。
     //继承属性,借调了超类(类型1)的构造函数。然后定义自己的属性。
     SuperType.call(this,name);
     this.age = age;
}

//继承方法,制定构造器函数,添加自己的方法。
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType();
SubType.prototype.sayAge = function(){
      alert(this.age);
};

var instance1 = new SubType("Dadaoshenyi",25);
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
instance1.sayName();//"Dadaoshenyi"
instance1.sayAge();//25

var instance1 = new SubType("Changyangzhe",25);
alert(instance1.colors);//"red,blue,green"
instance1.sayName();//"Changyangzhe"
instance1.sayAge();//25
//call方法,用于参数个数已知,改变了上下文环境。
语法:call([thisObj,[,arg1[, arg2[,   [,.argN]]]]]) 
定义:调用一个对象的一个方法,以另一个对象替换当前对象。

call 方法可以用来代替另一个对象调用一个方法call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。 

这里SuperType构造函数定义了两个属性:name和colors。SuperType的原型定义了一个方法sayName()。原本的类型2的constructor指向的是SuperType,这里重新指向类型2的实例是它自己SubType。

8、区别apply、call和bind:

都是更改函数的作用域上下文,bind使用apply来内部实现,返回一个函数,便于以后调用。apply和bind的传入的参数不同,立即调用。

转载于:https://www.cnblogs.com/changyangzhe/p/5721176.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值