JavaScript 中的所有事物都是对象:字符串、数字、数组、日期,等等。 在 JavaScript 中,对象是拥有属性和方法的数据。 例如: * * * 属性是与对象相关的值。 * * * 方法是能够在对象上执行的动作。 * * * 举例:汽车就是现实生活中的对象。
通过以上的结果我们可以知道 a1 a2 a3 为普通对象,f1 f2 f3 为函数对象。 区分:通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。 其实f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
1、在java中语言中,存在有类的概念,类就是对象的模板,对象就是类的实例。但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(prototype chains)实现的。但在ES6中引入了类(class)这个概念,作为对象的模板,新的class写法知识让原型对象的写法更加清晰,这里不重点谈这个
2.首先我们来详细了解下什么是构造器 构造函数的特点: a:构造函数的首字母必须大写,用来区分于普通函数 b:内部使用的this对象,来指向即将要生成的实例对象 c:使用New来生成实例对象
function Person(name,age){ this.name = name; this.age = age; this.sayHello = function(){ console.log(this.name +"say hello"); } } var boy1 = new Person("lily",23); boy.sayHello(); // lily say hello var boy2 = new Person('huahua',20)以上的两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person。 即:
console.log(boy1.constructor == Person); //true console.log(boy2.constructor == Person); //true构造函数的缺点: 所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个对象实例之间,无法共享属性
解决思路:
a:所有实例都会通过原型链引用到prototype
b:prototype相当于特定类型所有实例都可以访问到的一个公共容器
c:那么我们就将重复的东西放到公共容易就好了
构造函数有它自己的属性及其方法,其中包括自己定义的属性和方法外,还有两个特殊属性(prototype、constructor);而每个它的实例都会拥有它的所有属性和方法(包括prototype、constructor)constructor则是指向每个实例的构造函数,而prototype 原型则是一个地址指向原型对象,这个原型对象创建了实例后,只会取得constructor属性,其他的都是从Object继承而来;在Firefox 、 chrome在对象上都支持一个属性"proto";这个原型对象的属性和方法是所有该类实例共享的任何该类实例够可以访问该原型对象的属性和方法 它们之间的关系如下图:
Person是一个对象,它有一个prototype的原型属性(因为所有的对象都一prototype原型!)prototype属性有自己的prototype对象,而pototype对象肯定也有自己的constuct属性,construct属性有自己的constuctor对象,神奇的事情要发生了,这最后一个constructor对象就是我们构造出来的function函数本身!
1、在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。 2、prototype 属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。 3、实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
我们可以把原型对象先记为Person.prototype
Person.prototype = { name: 'Wendy', age: 20, sayName: function() { alert(this.name); } }下面来看个例子: 从下图中可以看出:prototype的属性值是一个对象,是属性的集合。
function Person(name,age){ this.name=name; this.age=age; } Person.prototype.sayHello=function(){ alert("使用原型得到Name:"+this.name); } var per=new Person("alin",21); per.sayHello(); //输出:使用原型得到Name:alin在函数Person里面自定义了属性name和age,而prototype是我们的属性集合,也就是说,我要添加sayHello这个属性到Person,则要这样写:Person.prototype.sayHello,就能添加Person的属性。
(我们可以简单的把prototype看做是一个模板,新创建的自定义对象都是这个模板prototype的一个拷贝,其实准确来说,不应该说是一个拷贝,而是一个连接,只不过这种链接是不可见,新实例化的对象内部有一个看不见的_Proto_指针,指向原型对象)。对象的 proto 属性:我们叫它 原型
注:大多数情况下 _proto_可以理解为“构造器的原型”,即: _proto_===constructor.prototype (通过Object.create0创建的对象不适用此等式,图2有说明)
_proto_的指向取决于对象创建时的实现方式。以下图表列出了三种常见方式创建对象后,_proto_分别指向谁。 1、字面量方式 var a=0; 2、构造器方式 var A=function(){}; var a=new A(); 3、Object.create方式 var a1={} var a2=Object.create(a1);
简单理解就是原型组成的链,由于__proto_是任何对象都有的属性,而js里万物皆对象,对象的__proto__是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了,并且最终值是null。 当is引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链上查找,但不会查找自身的prototype。也就是说函数拥有 prototype 属性,但是函数自己不用它
当函数对象本身的属性或方法与原型的属性或方法同名的时候: 1、默认调用的是函数对象本身的属性或方法. 2、通过原型增加的属性或方法的确是存在的. 3、函数对象本身的属性或方法的优先级要高于原型的属性或方法.
核心: 将父类的实例作为子类的原型
function Father(name,age){ this.name=name; this.age=age; } Father.prototype.eat=function(){ //给原型添加eat方法 console.log(this.name+"吃饭了"); } var f1=new Father("李四",20); //创建新对象f1, [[proto]]指向父原型 function Son(){ } Son.prototype=f1; //将子构造函数的prototype指向了父类型的对象,这里实现了——继承 var s1=new Son(); // 创建子对象 s1.eat(); //李四吃饭了①:当 Son.prototype指向Father的时候,他就已经是父类型的Son了。 ②:s1.eat();s1中没有此方法,该方法在父类型原型中,当s1访问时,现在s1中查找,若没有则向他指向的原型中去查找该方法,若还是没有,则继续往上面的原型中查找。这样就形成了一条原型链。 ③:通过原型链实现了继承。
简写形式: var f1=new Father; var Son.prototype=f1 //可以直接简写成: var Son.prototypr=new Father(); //这个时候可以传值进去 ,其余地方无法传值特点:
非常纯粹的继承关系,实例是子类的实例,也是父类的实例父类新增原型方法/原型属性,子类都能访问到简单,易于实现缺点:
要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中无法实现多继承来自原型对象的引用属性是所有实例共享的(详细请看附录代码: 示例1) 创建子类实例时,无法向父类构造函数传参核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true特点:
解决了1中,子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)
缺点:
实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
核心:为父类实例添加新特性,作为子类实例返回
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false特点:
不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:
实例是父类的实例,不是子类的实例 不支持多继承
特点:
支持多继承
缺点:
效率较低,内存占用高(因为要拷贝父类的属性) 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true特点:
弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 既是子类的实例,也是父类的实例 不存在引用属性共享问题 可传参 函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Fn(name,age){ this.name=name; //构造函数的属性多样 this.age=age; if((typeof Fn.prototype.eat)!="funciton"){ //判断语句中是否有该方法,没有则创建 Fn.prototype.eat=function(){ //原型的方法共享 console.log(this.name+"吃了饭"); } } } function Son(name,age,sex){ //创建子类构造函数 Fn.call(this,name,age) //借调Fn()的属性 this.sex=sex; }; Son.prototype=new Fn(); //Son.prototype指向父类对象,实现了继承,所以能够调用eat方法, var s1=new Son("李四",20,"男"); //若没有继承,单单的使用call借调Fn继承,子类实例s1无法调用eat方法 callconsole.log(s1); //因为call不是真正的继承 s1.eat();特点:
堪称完美
缺点:
实现较为复杂