必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

可以理解A将B中的方法和属性全部克隆了一遍, 

2019-10-05 05:36 来源:未知

前端基础进阶(9):详解面向对象、构造函数、原型与原型链

2017/04/02 · JavaScript · 1 评论 · 原型, 原型链, 构造函数, 面向对象

原文出处: 波同学   

图片 1

.

如果要我总结一下学习前端以来我遇到了哪些瓶颈,那么面向对象一定是第一个毫不犹豫想到的。尽管我现在对于面向对象有了一些的了解,但是当初的那种似懂非懂的痛苦,依然历历在目。

为了帮助大家能够更加直观的学习和了解面向对象,我会用尽量简单易懂的描述来展示面向对象的相关知识。并且也准备了一些实用的例子帮助大家更加快速的掌握面向对象的真谛。

  • jQuery的面向对象实现
  • 封装拖拽
  • 简易版运动框架封装

这可能会花一点时间,但是却值得期待。所以如果有兴趣的朋友可以来简书和公众号关注我。

而这篇文章主要来聊一聊关于面向对象的一些重要的基本功。

6. 组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但又同时共享着对方法的引用。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["aa", "bb", "cc"];
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        console.log(this.name);
    }
};
var person1 = new Person("Brittany", 23, "Web front-end Engineer");
person1.friends.push("dd");     //["aa", "bb", "cc", "dd"]
console.log(person1.friends);
var person2 = new Person("Sam", 26, "Web front-end Engineer");
console.log(person2.friends);   //["aa", "bb", "cc"]
console.log(person1.friends == person2.friends);  //false
console.log(person1.sayName == person2.sayName);  //true

 

 

时间:2014-10-21

地点:合肥

引用:《JavaScript高级程序设计》 

    var Bro = new Browser();
    Bro.run();
</script>

相关文章

相关搜索:

今天看啥

搜索技术库

返回首页

  • IOS8天气应用可以显示细节数据技巧
  • CAD迷你看图如何查看设备清单
  • 华为Mate7如何开启按电源键结束通话
  • 电脑回收站无法调整容量的大小怎么办?
  • 新三板是什么意思
  • 如何在win7旗舰版电脑中删除U盘使用历史记录

相关频道: HTML/CSS  HTML5  Javascript  jQuery  AJax教程  前端代码  正则表达式  Flex教程  WEB前端教程  

  利用原型让一个引用类型继承另一个引用类型的属性和方法。

四、原型链

原型对象其实也是普通的对象。几乎所有的对象都可能是原型对象,也可能是实例对象,而且还可以同时是原型对象与实例对象。这样的一个对象,正是构成原型链的一个节点。因此理解了原型,那么原型链并不是一个多么复杂的概念。

我们知道所有的函数都有一个叫做toString的方法。那么这个方法到底是在哪里的呢?

先随意声明一个函数:

function foo() {}

1
function foo() {}

那么我们可以用如下的图来表示这个函数的原型链。

图片 2

原型链

其中foo是Function对象的实例。而Function的原型对象同时又是Object的实例。这样就构成了一条原型链。原型链的访问,其实跟作用域链有很大的相似之处,他们都是一次单向的查找过程。因此实例对象能够通过原型链,访问到处于原型链上对象的所有属性与方法。这也是foo最终能够访问到处于Object原型对象上的toString方法的原因。

基于原型链的特性,我们可以很轻松的实现继承

2)原型与in操作符

in单独使用时,通过对象访问到特定属性时返回true,无论该属性存在于实例中还是原型中。hasOwnProperty(),通过对象访问到特定属性时返回true,且该属性存在于实例中。 

var p3 = new Person();
console.log("name" in p3);                //true
console.log(p3.hasOwnProperty("name"));   //false
p3.name = "insist";
console.log(p3.hasOwnProperty("name"));   //true

确定属性到底是存在于对象中,还是存在于原型中。如下函数hasPrototypePropery()返回true表示该属性存在于原型中,而不是存在于实例中。

function hasPrototypeProperty(object, name) {
    return !hasOwnProperty("name") && (name in object);
}

for..in循环,所有通过对象能够访问的,可枚举的(enumerated)属性,既包括存在于实例中的属性,也包括存在于原型中的属性。

for(var prop in p1) {
    console.log(prop);                    //name age job sayName
}

Object.keys(),ECMAScript5的方法,取得对象上所有可枚举的属性,接收一个对象作为参数,返回值是一个包含所有可枚举属性的字符串数组。注意:Person.prototype也是对象。

var keys = Object.keys(Person.prototype);
console.log(keys);             //["name age job sayName"]
var p1 = new Person(); 
console.log(Object.keys(p1));  //[]
p1.name = "get";
console.log(Object.keys(p1));  //["name"]

Object.getOwnPropertyNames(),得到所有实例属性,无论它是否可枚举。

var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys);     //["constructor", "name", "age", "job", "getName"] 
var keys_p1 = Object.getOwnPropertyNames(p1);
console.log(keys_p1);  //[]

<script type="text/javascript">
    function Box(){             //被继承的函数叫做超类型(父类,基类)
        this.name = "Jack";
    }

JavaScript中原型和原型链详解

 这篇文章主要介绍了JavaScript中原型和原型链详解,本文讲解了私有变量和函数、静态变量和函数、实例变量和函数、原型和原型链的基本概念,需要的朋友可以参考下

 

 

javascript中的每个对象都有一个内置的属性prototype,Javascript中对象的prototype属性的解释是:返回对象类型原型的引用。意思是是prototype属性保存着对另一个JavaScript对象的引用,这个对象作为当前对象的父对象。

复制代码 代码如下:

A.prototype = new B();
理解prototype不应把它和继承混淆。A的prototype为B的一个实例,可以理解A将B中的方法和属性全部克隆了一遍。A能使用B的方法和属性。这里强调的是克隆而不是继承。可以出现这种情况:A的prototype是B的实例,同时B的prototype也是A的实例。

 

继续看下面的分析:

私有变量和函数

在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量和函数。

复制代码 代码如下:

<script type="text/javascript">
function Box(){
var color = "blue";//私有变量
var fn = function() //私有函数
{

 

}
}
</script>

这样在函数对象Box外部无法访问变量color和fn,他们就变成私有的了:

复制代码 代码如下:

var obj = new Box();
alert(obj.color);//弹出 undefined
alert(obj.fn);//同上

 

静态变量和函数

当定义一个函数后通过点号 “.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数。

 

复制代码 代码如下:

<script type="text/javascript">
function Obj(){};

 

Obj.num = 72;//静态变量
Obj.fn = function() //静态函数
{

}

alert(Obj.num);//72
alert(typeof Obj.fn)//function

var t = new Obj();
alert(t.name);//undefined
alert(typeof t.fn);//undefined
</script>

 

实例变量和函数

在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,实例化后可以访问,js也能做到这样

复制代码 代码如下:

<script type="text/javascript">
function Box(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法

 

}
}

console.log(typeof Box.a); //undefined
console.log(typeof Box.fn); //undefined

var box=new Box();
console.log(typeof box.a); //object
console.log(typeof box.fn); //function
</script>

 

为实例变量和方法添加新的方法和属性

复制代码 代码如下:

<script type="text/javascript">
function Box(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法

 

}
}

var box1=new Box();
box1.a.push(1);
box1.fn={};
console.log(box1.a); //[1]
console.log(typeof box1.fn); //object

var box2=new Box();
console.log(box2.a); //[]
console.log(typeof box2.fn); //function
</script>

 

在box1中修改了a和fn,而在box2中没有改变,由于数组和函数都是对象,是引用类型,这就说明box1中的属性和方法与box2中的属性与方法虽然同名但却不是一个引用,而是对Box对象定义的属性和方法的一个复制。

这个对属性来说没有什么问题,但是对于方法来说问题就很大了,因为方法都是在做完全一样的功能,但是却又两份复制,如果一个函数对象有上千和实例方法,那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的,这可肿么办呢,prototype应运而生。

基本概念

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

使用原型的好处是可以让对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中。使用构造函数的主要问题就是每个方法都要在每个实例中创建一遍。

在JavaScript中,一共有两种类型的值,原始值和对象值。每个对象都有一个内部属性 prototype ,我们通常称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象也一定有自己的原型。这样就形成了一条线性的链,我们称之为原型链。

含义

函数可以用来作为构造函数来使用。另外只有函数才有prototype属性并且可以访问到,但是对象实例不具有该属性,只有一个内部的不可访问的__proto__属性。__proto__是对象中一个指向相关原型的神秘链接。按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。

复制代码 代码如下:

<script type="text/javascript">
var Browser = function(){};
Browser.prototype.run = function(){
alert("I'm Gecko,a kernel of firefox");
}

 

var Bro = new Browser();
Bro.run();
</script>

 

当我们调用Bro.run()方法时,由于Bro中没有这个方法,所以,他就会去他的__proto__中去找,也就是Browser.prototype,所以最终执行了该run()方法。(在这里,函数首字母大写的都代表构造函数,以用来区分普通函数)

当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。

复制代码 代码如下:

<script type="text/javascript">
function Person(name){ //构造函数
this.name=name;
}

 

Person.prototype.printName=function() //原型对象
{
alert(this.name);
}

var person1=new Person('Byron');//实例化对象
console.log(person1.__proto__);//Person
console.log(person1.constructor);//自己试试看会是什么吧
console.log(Person.prototype);//指向原型对象Person
var person2=new Person('Frank');
</script>

Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的:

 

图片 3

每个JavaScript函数都有prototype属性,这个属性引用了一个对象,这个对象就是原型对象。原型对象初始化的时候是空的,我们可以在里面自定义任何属性和方法,这些方法和属性都将被该构造函数所创建的对象继承。

那么,现在问题来了。构造函数、实例和原型对象三者之间有什么关系呢?

构造函数、实例和原型对象的区别

实例就是通过构造函数创建的。实例一创造出来就具有constructor属性(指向构造函数)和__proto__属性(指向原型对象),

构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。

原型对象内部也有一个指针(constructor属性)指向构造函数:Person.prototype.constructor = Person;

实例可以访问原型对象上定义的属性和方法。

在这里person1和person2就是实例,prototype是他们的原型对象。

再举个栗子:

复制代码 代码如下:

<script type="text/javascript">
function Animal(name) //积累构造函数
{
this.name = name;//设置对象属性
}

 

Animal.prototype.behavior = function() //给基类构造函数的prototype添加behavior方法
{
alert("this is a "+this.name);
}

var Dog = new Animal("dog");//创建Dog对象
var Cat = new Animal("cat");//创建Cat对象

Dog.behavior();//通过Dog对象直接调用behavior方法
Cat.behavior();//output "this is a cat"

alert(Dog.behavior==Cat.behavior);//output true;
</script>

 

可以从程序运行结果看出,构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。(可以试一下将Animal.prototype.behavior 中的prototype属性去掉,看看还能不能运行。)在这里,prototype属性指向Animal对象。

数组对象实例

再看个数组对象的实例。当我们创建出array1这个对象的时候,array1实际在Javascript引擎中的对象模型如下:

复制代码 代码如下:

var array1 = [1,2,3];

 

图片 4

array1对象具有一个length属性值为3,但是我们可以通过如下的方法来为array1增加元素:

复制代码 代码如下:

array1.push(4);
push这个方法来自于array1的__proto__成员指向对象的一个方法(Array.prototye.push())。正是因为所有的数组对象(通过[]来创建的)都包含有一个指向同一个具有push,reverse等方法对象(Array.prototype)的__proto__成员,才使得这些数组对象可以使用push,reverse等方法。

 

函数对象实例

复制代码 代码如下:

function Base() {
this.id = "base"
}

 

图片 5

 

复制代码 代码如下:

var obj = new Base();
这样代码的结果是什么,我们在Javascript引擎中看到的对象模型是:

 

图片 6

new操作符具体干了什么呢?其实很简单,就干了三件事情。

复制代码 代码如下:

var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

 

原型链

原型链:当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找,如果prototype没有,就会去prototype关联的前辈prototype那里寻找,如果再没有则继续查找Prototype.Prototype引用的对象,依次类推,直到Prototype.….Prototype为undefined(Object的Prototype就是undefined)从而形成了所谓的“原型链”。

 

复制代码 代码如下:

<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}

 

TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();
</script>

 

这里,用构造器Shape()新建了一个实体,然后用它去覆盖该对象的原型。

复制代码 代码如下:

<script type="text/javascript">
function Shape(){
this.name = "shape";
this.toString = function(){
return this.name;
}
}
function TwoShape(){
this.name = "2 shape";
}
function Triangle(side,height){
this.name = "Triangle";
this.side = side;
this.height = height;
this.getArea = function(){
return this.side*this.height/2;
}
}

 

TwoShape.prototype = new Shape();
Triangle.prototype = new TwoShape();

TwoShape.prototype.constructor = TwoShape;
Triangle.prototype.constructor = Triangle;

var my = new Triangle(5,10);
my.getArea();
my.toString();//Triangle
my.constructor;//Triangle(side,height)
</script>

 

原型继承

原型继承:在原型链的末端,就是Object构造函数prototype属性指向的那个原型对象。这个原型对象是所有对象的祖先,这个老祖宗实现了诸如toString等所有对象天生就该具有的方法。其他内置构造函数,如Function,Boolean,String,Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些特征。

ECMAScript中,实现继承的方法就是依靠原型链实现的。

复制代码 代码如下:

<script type="text/javascript">
function Box(){ //被继承的函数叫做超类型(父类,基类)
this.name = "Jack";
}

 

function Tree(){ //继承的函数叫做子类型(子类,派生类)
this.age = 300;
}
//通过原型链继承,赋值给子类型的原型属性
//new Box()会将box构造里的信息和原型里的信息都交给Tree
Tree.prototype = new Box();//Tree继承了Box,通过原型,形成链条

var tree = new Tree();
alert(tree.name);//弹出 Jack
</script>

 

原型链的问题:原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中最主要的问题来自包含引用类型的值原型。包含引用类型的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上回变成另一个类型的实例。于是,原先的实例属性也就变成了原型的属性。

在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。再加上刚刚讨论的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

再举个栗子:

 

复制代码 代码如下:

<script type="text/javascript">
function Person(name)
{
this.name = name;//设置对象属性
};

 

Person.prototype.company = "Microsoft";//设置原型的属性
Person.prototype.SayHello = function() //原型的方法
{
alert("Hello,I'm "+ this.name+ " of " + this.company);
};

var BillGates = new Person("BillGates");//创建person对象
BillGates.SayHello();//继承了原型的内容,输出"Hello,I'm BillGates of Microsoft"

var Jobs = new Person("Jobs");
Jobs.company = "Apple";//设置自己的company属性,掩盖了原型的company属性
Jobs.SayHello = function()
{
alert("Hi,"+this.name + " like " + this.company);
};
Jobs.SayHello();//自己覆盖的属性和方法,输出"Hi,Jobs like Apple"
BillGates.SayHello();//Jobs的覆盖没有影响原型,BillGates还是照样输出
</script>

 

看下面一个原型链例子:

 

复制代码 代码如下:

<script type="text/javascript">
function Year(){
this.value = 21;
}
Year.prototype = {
method:function(){

 

}
};

function Hi(){

};
//设置Hi的prototype属性为Year的实例对象
Hi.prototype = new Year();
Hi.prototype.year = 'Hello World';

Hi.prototype.constructor = Hi;

var test = new Hi();//创建一个Hi的新实例

//原型链
test [Hi的实例]
Hi.prototype [Year的实例]
{year:'Hello World'}
Year.prototype
{method:……};
object.prototype
{toString:...};

</script>

 

从上面例子中,test对象从Hi.prototype和Year.prototype中继承下来;因此他能访问Year的原型方法method,同时他能访问实例属性value

__ptoto__属性

__ptoto__属性(IE浏览器不支持)是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor,通过这两个属性,就可以访问原型里的属性和方法了。

Javascript中的对象实例本质上是由一系列的属性组成的,在这些属性中,有一个内部的不可见的特殊属性——__proto__,该属性的值指向该对象实例的原型,一个对象实例只拥有一个唯一的原型。

 

复制代码 代码如下:

<script type="text/javascript">
function Box(){ //大写,代表构造函数
Box.prototype.name = "trigkit4";//原型属性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}

 

var box1 = new Box();
var box2 = new Box();
alert(box1.constructor);//构造属性,可以获取构造函数本身,
//作用是被原型指针定位,然后得到构造函数本身
</script>

 

__proto__属性和prototype属性的区别

prototype是function对象中专有的属性。
__proto__是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
__ptoto__实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。__ptoto__只能在学习或调试的环境下使用。

原型模式的执行流程

1.先查找构造函数实例里的属性或方法,如果有,就立即返回。
2.如果构造函数的实例没有,就去它的原型对象里找,如果有,就立即返回

原型对象的

复制代码 代码如下:

<script type="text/javascript">
function Box(){ //大写,代表构造函数
Box.prototype.name = "trigkit4";//原型属性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}
}

 

var box1 = new Box();
alert(box1.name);//trigkit4,原型里的值
box1.name = "Lee";
alert(box1.name);//Lee,就进原则

var box2 = new Box();
alert(box2.name);//trigkit4,原型的值,没有被box1修改
</script>

 

构造函数的

复制代码 代码如下:

<script type="text/javascript">
function Box(){
this.name = "Bill";
}

 

Box.prototype.name = "trigkit4";//原型属性
Box.prototype.age = "21";
Box.prototype.run = function()//原型方法
{
return this.name + this.age + 'studying';
}

var box1 = new Box();
alert(box1.name);//Bill,原型里的值
box1.name = "Lee";
alert(box1.name);//Lee,就进原则
</script>

 

综上,整理一下:

复制代码 代码如下:

<script type="text/javascript">
function Person(){};

 

Person.prototype.name = "trigkit4";
Person.prototype.say = function(){
alert("Hi");
}

var p1 = new Person();//prototype是p1和p2的原型对象
var p2 = new Person();//p2为实例化对象,其内部有一个__proto__属性,指向Person的prototype

console.log(p1.prototype);//undefined,这个属性是一个对象,访问不到
console.log(Person.prototype);//Person
console.log(Person.prototype.constructor);//原型对象内部也有一个指针(constructor属性)指向构造函数
console.log(p1.__proto__);//这个属性是一个指针指向prototype原型对象
p1.say();//实例可以访问到在原型对象上定义的属性和方法

</script>

 

工厂模式

复制代码 代码如下:

function createObject(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}

 

工厂模式解决了实例化对象大量重复的问题,但还有一个问题,那就是根本无法搞清楚他们到底是哪个对象的实例。
使用构造函数的方法,既解决了重复实例化的问题,又解决了对象识别的问题。

使用构造函数的方法和工厂模式的不同之处在于:

1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象
3.没有return 语句

当使用了构造函数,并且new 构造函数(),那么就在后台执行了new Object();
函数体内的this代表了new Object()出来的对象

1.判断属性是在构造函数的实例里,还是在原型里,可以使用`hasOwnProperty()`函数
2.字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反
为什么指向Object?因为Box.prototype = {};这种写法其实就是创建了一个新对象。
而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性
3.如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的
4.如果是原型方法,那么他们的地址的共享的

这篇文章主要介绍了JavaScript中原型和原型链详解,本文讲解了私有变量和函数、静态变量和函数、实例变量和函...

      p1 instanceof Object   // true

三、构造函数

在JavaScript中,new关键字可以让一个函数变得与众不同。通过下面的例子,我们来一探new关键字的神奇之处。

function demo() { console.log(this); } demo(); // window new demo(); // demo

1
2
3
4
5
6
function demo() {
    console.log(this);
}
 
demo();  // window
new demo();  // demo

为了能够直观的感受他们不同,建议大家动手实践观察一下。很显然,使用new之后,函数内部发生了一些变化,让this指向改变。那么new关键字到底做了什么事情呢。嗯,其实我之前在文章里用文字大概表达了一下new到底干了什么,但是一些同学好奇心很足,总期望用代码实现一下,我就大概以我的理解来表达一下吧。

// 先一本正经的创建一个构造函数,其实该函数与普通函数并无区别 var Person = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } } // 将构造函数以参数形式传入 function New(func) { // 声明一个中间对象,该对象为最终返回的实例 var res = {}; if (func.prototype !== null) { // 将实例的原型指向构造函数的原型 res.__proto__ = func.prototype; } // ret为构造函数执行的结果,这里通过apply,将构造函数内部的this指向修改为指向res,即为实例对象 var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)); // 当我们在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象 if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { return ret; } // 如果没有明确指定返回对象,则默认返回res,这个res就是实例对象 return res; } // 通过new声明创建实例,这里的p1,实际接收的正是new中返回的res var p1 = New(Person, 'tom', 20); console.log(p1.getName()); // 当然,这里也可以判断出实例的类型了 console.log(p1 instanceof Person); // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 先一本正经的创建一个构造函数,其实该函数与普通函数并无区别
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}
 
// 将构造函数以参数形式传入
function New(func) {
 
    // 声明一个中间对象,该对象为最终返回的实例
    var res = {};
    if (func.prototype !== null) {
 
        // 将实例的原型指向构造函数的原型
        res.__proto__ = func.prototype;
    }
 
    // ret为构造函数执行的结果,这里通过apply,将构造函数内部的this指向修改为指向res,即为实例对象
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
 
    // 当我们在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
 
    // 如果没有明确指定返回对象,则默认返回res,这个res就是实例对象
    return res;
}
 
// 通过new声明创建实例,这里的p1,实际接收的正是new中返回的res
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());
 
// 当然,这里也可以判断出实例的类型了
console.log(p1 instanceof Person); // true

JavaScript内部再通过其他的一些特殊处理,将var p1 = New(Person, 'tom', 20); 等效于var p1 = new Person('tom', 20);。就是我们认识的new关键字了。具体怎么处理的,我也不知道,别刨根问底了,一直回答下去太难 – -!

老实讲,你可能很难在其他地方看到有如此明确的告诉你new关键字到底对构造函数干了什么的文章了。理解了这段代码,你对JavaScript的理解又比别人深刻了一分,所以,一本正经厚颜无耻求个赞可好?

当然,很多朋友由于对于前面几篇文章的知识理解不够到位,会对new的实现表示非常困惑。但是老实讲,如果你读了我的前面几篇文章,一定会对这里new的实现有似曾相识的感觉。而且我这里已经尽力做了详细的注解,剩下的只能靠你自己了。

但是只要你花点时间,理解了他的原理,那么困扰了无数人的构造函数中this到底指向谁就变得非常简单了。

所以,为了能够判断实例与对象的关系,我们就使用构造函数来搞定。

var Person = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } } var p1 = new Person('Ness', 20); console.log(p1.getName()); // Ness console.log(p1 instanceof Person); // true

1
2
3
4
5
6
7
8
9
10
11
12
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}
 
var p1 = new Person('Ness', 20);
console.log(p1.getName());  // Ness
 
console.log(p1 instanceof Person); // true

关于构造函数,如果你暂时不能够理解new的具体实现,就先记住下面这几个结论吧。

  • 与普通函数相比,构造函数并没有任何特别的地方,首字母大写只是我们约定的小规定,用于区分普通函数;
  • new关键字让构造函数具有了与普通函数不同的许多特点,而new的过程中,执行了如下过程:
    1. 声明一个中间对象;
    2. 将该中间对象的原型指向构造函数的原型;
    3. 将构造函数的this,指向该中间对象;
    4. 返回该中间对象,即返回实例对象。

4)原型的动态性

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

var friend = new Person();
Person.prototype.sayHi = function() {
    console.log("hi");
};
friend.sayHi();

尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,情况就不一样了。

function Person() {}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name: "Brittany",
    age: 23,
    job: "Web front-end engineer",
    getName: function() {
        console.log(this.name);
    }
};
friend.getName();                  //error

若是创建实例放在重写原型对象之后,则不会报错。

javascript中的每个对象都有一个内置的属性prototype,Javascript中对象的prototype属性的解释是:返回对象类型原型的引用。意思是是prototype属性保存着对另一个JavaScript对象的引用,这个对象作为当前对象的父对象。

帮客评论

  Person.apply(o, ['lover', 1, null]);  // {name: "lover", age: 1, gender: null, sayName: ƒ} 

一、对象的定义

在ECMAScript-262中,对象被定义为“无序属性的集合,其属性可以包含基本值,对象或者函数”

也就是说,在JavaScript中,对象无非就是由一些列无序的key-value对组成。其中value可以是基本值,对象或者函数。

// 这里的person就是一个对象 var person = { name: 'Tom', age: 18, getName: function() {}, parent: {} }

1
2
3
4
5
6
7
// 这里的person就是一个对象
var person = {
    name: 'Tom',
    age: 18,
    getName: function() {},
    parent: {}
}

创建对象

我们可以通过new的方式创建一个对象。

var obj = new Object();

1
var obj = new Object();

也可以通过对象字面量的形式创建一个简单的对象。

var obj = {};

1
var obj = {};

当我们想要给我们创建的简单对象添加方法时,可以这样表示。

// 可以这样 var person = {}; person.name = "TOM"; person.getName = function() { return this.name; } // 也可以这样 var person = { name: "TOM", getName: function() { return this.name; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以这样
var person = {};
person.name = "TOM";
person.getName = function() {
    return this.name;
}
 
// 也可以这样
var person = {
    name: "TOM",
    getName: function() {
        return this.name;
    }
}

访问对象的属性和方法

假如我们有一个简单的对象如下:

var person = { name: 'TOM', age: '20', getName: function() { return this.name } }

1
2
3
4
5
6
7
var person = {
    name: 'TOM',
    age: '20',
    getName: function() {
        return this.name
    }
}

当我们想要访问他的name属性时,可以用如下两种方式访问。

person.name // 或者 person['name']

1
2
3
4
person.name
 
// 或者
person['name']

如果我们想要访问的属性名是一个变量时,常常会使用第二种方式。例如我们要同时访问person的name与age,可以这样写:

['name', 'age'].forEach(function(item) { console.log(person[item]); })

1
2
3
['name', 'age'].forEach(function(item) {
    console.log(person[item]);
})

这种方式一定要重视,记住它以后在我们处理复杂数据的时候会有很大的帮助。

3. 工厂模式 

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name);
    };
    return o;
}

var person1 = createPerson("Brittany", 23, "Software Engineer");
var person2 = createPerson("Sam", 26, "Software Engineer");
console.log(typeof person1);   //Object

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。如代码中只能检测出person1为Object类型。随着JavaScript的发展,又一个新模式出现了。

<script type="text/javascript">
    function Box(){
        var color = "blue";//私有变量
        var fn = function() //私有函数
        {

  var keys = Object.keys(Person.prototype);

四、原型

虽然构造函数解决了判断实例类型的问题,但是,说到底,还是一个对象的复制过程。跟工厂模式颇有相似之处。也就是说,当我们声明了100个person对象,那么就有100个getName方法被重新生成。

这里的每一个getName方法实现的功能其实是一模一样的,但是由于分别属于不同的实例,就不得不一直不停的为getName分配空间。这就是工厂模式存在的第二个麻烦。

显然这是不合理的。我们期望的是,既然都是实现同一个功能,那么能不能就让每一个实例对象都访问同一个方法?

当然能,这就是原型对象要帮我们解决的问题了。

我们创建的每一个函数,都可以有一个prototype属性,该属性指向一个对象。这个对象,就是我们这里说的原型。

当我们在创建对象时,可以根据自己的需求,选择性的将一些属性和方法通过prototype属性,挂载在原型对象上。而每一个new出来的实例,都有一个__proto__属性,该属性指向构造函数的原型对象,通过这个属性,让实例对象也能够访问原型对象上的方法。因此,当所有的实例都能够通过__proto__访问到原型对象时,原型对象的方法与属性就变成了共有方法与属性。

我们通过一个简单的例子与图示,来了解构造函数,实例与原型三者之间的关系。

由于每个函数都可以是构造函数,每个对象都可以是原型对象,因此如果在理解原型之初就想的太多太复杂的话,反而会阻碍你的理解,这里我们要学会先简化它们。就单纯的剖析这三者的关系。

// 声明构造函数 function Person(name, age) { this.name = name; this.age = age; } // 通过prototye属性,将方法挂载到原型对象上 Person.prototype.getName = function() { return this.name; } var p1 = new Person('tim', 10); var p2 = new Person('jak', 22); console.log(p1.getName === p2.getName); // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 声明构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}
 
// 通过prototye属性,将方法挂载到原型对象上
Person.prototype.getName = function() {
    return this.name;
}
 
var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
console.log(p1.getName === p2.getName); // true

图片 7

图示

通过图示我们可以看出,构造函数的prototype与所有实例对象的__proto__都指向原型对象。而原型对象的constructor指向构造函数。

除此之外,还可以从图中看出,实例对象实际上对前面我们所说的中间对象的复制,而中间对象中的属性与方法都在构造函数中添加。于是根据构造函数与原型的特性,我们就可以将在构造函数中,通过this声明的属性与方法称为私有变量与方法,它们被当前被某一个实例对象所独有。而通过原型声明的属性与方法,我们可以称之为共有属性与方法,它们可以被所有的实例对象访问。

当我们访问实例对象中的属性或者方法时,会优先访问实例对象自身的属性和方法。

function Person(name, age) { this.name = name; this.age = age; this.getName = function() { console.log('this is constructor.'); } } Person.prototype.getName = function() { return this.name; } var p1 = new Person('tim', 10); p1.getName(); // this is constructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        console.log('this is constructor.');
    }
}
 
Person.prototype.getName = function() {
    return this.name;
}
 
var p1 = new Person('tim', 10);
 
p1.getName(); // this is constructor.

在这个例子中,我们同时在原型与构造函数中都声明了一个getName函数,运行代码的结果表示原型中的访问并没有被访问。

我们还可以通过in来判断,一个对象是否拥有某一个属性/方法,无论是该属性/方法存在与实例对象还是原型对象。

function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; } var p1 = new Person('tim', 10); console.log('name' in p1); // true

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
    this.name = name;
    this.age = age;
}
 
Person.prototype.getName = function() {
    return this.name;
}
 
var p1 = new Person('tim', 10);
 
console.log('name' in p1); // true

in的这种特性最常用的场景之一,就是判断当前页面是否在移动端打开。

isMobile = 'ontouchstart' in document; // 很多人喜欢用浏览器UA的方式来判断,但并不是很好的方式

1
2
3
isMobile = 'ontouchstart' in document;
 
// 很多人喜欢用浏览器UA的方式来判断,但并不是很好的方式

更简单的原型写法

根据前面例子的写法,如果我们要在原型上添加更多的方法,可以这样写:

function Person() {} Person.prototype.getName = function() {} Person.prototype.getAge = function() {} Person.prototype.sayHello = function() {} ... ...

1
2
3
4
5
6
function Person() {}
 
Person.prototype.getName = function() {}
Person.prototype.getAge = function() {}
Person.prototype.sayHello = function() {}
... ...

除此之外,我还可以使用更为简单的写法。

function Person() {} Person.prototype = { constructor: Person, getName: function() {}, getAge: function() {}, sayHello: function() {} }

1
2
3
4
5
6
7
8
function Person() {}
 
Person.prototype = {
    constructor: Person,
    getName: function() {},
    getAge: function() {},
    sayHello: function() {}
}

这种字面量的写法看上去简单很多,但是有一个需要特别注意的地方。Person.prototype = {}实际上是重新创建了一个{}对象并赋值给Person.prototype,这里的{}并不是最初的那个原型对象。因此它里面并不包含constructor属性。为了保证正确性,我们必须在新创建的{}对象中显示的设置constructor的指向。即上面的constructor: Person

6)原型对象的问题

缺点一:省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下将取得相同的属性值。

缺点二:原型中所有属性被许多实例共享,这种共享对于函数非常适合。对于包含基本值属性倒也说得过去,因为通过在实例上添加一个同名属性,可以隐藏原型中对应的属性。但对于包含引用类型值得属性来说,问题比较突出。

function Person() { }
Person.prototype = {
    constructor: Person,
    name: "Brittany",
    friends: ["pink", "judy", "sam"],
    age: 23,
    job: "Web front-end engineer",
    getName: function() {
        console.log(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("leo");
console.log(person1.friends);           //["pink", "judy", "sam", "leo"]
console.log(person2.friends);           //["pink", "judy", "sam", "leo"]
console.log(Person.prototype.friends);  //["pink", "judy", "sam", "leo"]
person1.age = 35;
console.log(person1.age);           //35
console.log(person2.age);           //23
console.log(Person.prototype.age);  //23

person1的friends属性修改影响了person2的friends,但是person1的age属性修改并未影响person2的age属性。

原因在于:friends数组存在于Person.prototype中而非person1中,因此修改也会通过person2.friends(与person1.friends指向同一个数组)反映出来。而age属性在person1中也存在一份,修改的age属性只是修改person1中的,并不能修改Person.prototype中的age属性。

基本概念

  Person.prototype = {
    constructor: 'Person',
    sayName: function() {
      console.log(this.name);
    }
  }

六、总结

关于面向对象的基础知识大概就是这些了。我从最简单的创建一个对象开始,解释了为什么我们需要构造函数与原型,理解了这其中的细节,有助于我们在实际开发中灵活的组织自己的对象。因为我们并不是所有的场景都会使用构造函数或者原型来创建对象,也许我们需要的对象并不会声明多个实例,或者不用区分对象的类型,那么我们就可以选择更简单的方式。

我们还需要关注构造函数与原型的各自特性,有助于我们在创建对象时准确的判断我们的属性与方法到底是放在构造函数中还是放在原型中。如果没有理解清楚,这会给我们在实际开发中造成非常大的困扰。

最后接下来的几篇文章,我会挑几个面向对象的例子,继续帮助大家掌握面向对象的实际运用。

2 赞 4 收藏 1 评论

图片 8

1)对象创建 

function Person() {}
Person.prototype.name = "Brittany";
Person.prototype.age = 23;
Person.prototype.job = "Web front-end engineer";
Person.prototype.getName = function() {
    console.log(this.name);
};
var p1 = new Person();
var p2 = new Person();

console.log(p1.name);                    //Brittany
console.log(p2.name);                    //Brittany
console.log(p1.getName == p2.getName);   //true

实例中创建的属性会覆盖原型中的同名属性,不能修改原型中的属性。

p1.name = "Susan";
console.log(p1.name);                    //Susan

hasOwnProperty()检测一个属性是否存在于实例中。

console.log(p2.hasOwnProperty("name"));  //false
p2.name = "koko";
console.log(p2.hasOwnProperty("name"));  //true
delete p2.name;
console.log(p2.hasOwnProperty("name"));  //false

isPropertyOf()

console.log(Person.prototype.isPrototypeOf(p1));   //true
console.log(Person.prototype.isPrototypeOf(p2));   //true

getPropertyOf()

console.log(Object.getPrototypeOf(p1) == Person.prototype);  //true
console.log(Object.getPrototypeOf(p1));                      //Person

    function Tree(){          //继承的函数叫做子类型(子类,派生类)
        this.age = 300;
    }
    //通过原型链继承,赋值给子类型的原型属性
    //new Box()会将box构造里的信息和原型里的信息都交给Tree
    Tree.prototype = new Box();//Tree继承了Box,通过原型,形成链条

  var p1 = new Person('xhk', 36, '男'); // Person {name: "xhk", age: 36, gender: "男", sayName: ƒ}

二、工厂模式

使用上面的方式创建对象很简单,但是在很多时候并不能满足我们的需求。就以person对象为例。假如我们在实际开发中,不仅仅需要一个名字叫做TOM的person对象,同时还需要另外一个名为Jake的person对象,虽然他们有很多相似之处,但是我们不得不重复写两次。

var perTom = { name: 'TOM', age: 20, getName: function() { return this.name } }; var perJake = { name: 'Jake', age: 22, getName: function() { return this.name } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var perTom = {
    name: 'TOM',
    age: 20,
    getName: function() {
        return this.name
    }
};
 
var perJake = {
    name: 'Jake',
    age: 22,
    getName: function() {
        return this.name
    }
}

很显然这并不是合理的方式,当相似对象太多时,大家都会崩溃掉。

我们可以使用工厂模式的方式解决这个问题。顾名思义,工厂模式就是我们提供一个模子,然后通过这个模子复制出我们需要的对象。我们需要多少个,就复制多少个。

var createPerson = function(name, age) { // 声明一个中间对象,该对象就是工厂模式的模子 var o = new Object(); // 依次添加我们需要的属性与方法 o.name = name; o.age = age; o.getName = function() { return this.name; } return o; } // 创建两个实例 var perTom = createPerson('TOM', 20); var PerJake = createPerson('Jake', 22);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var createPerson = function(name, age) {
 
    // 声明一个中间对象,该对象就是工厂模式的模子
    var o = new Object();
 
    // 依次添加我们需要的属性与方法
    o.name = name;
    o.age = age;
    o.getName = function() {
        return this.name;
    }
 
    return o;
}
 
// 创建两个实例
var perTom = createPerson('TOM', 20);
var PerJake = createPerson('Jake', 22);

相信上面的代码并不难理解,也不用把工厂模式看得太过高大上。很显然,工厂模式帮助我们解决了重复代码上的麻烦,让我们可以写很少的代码,就能够创建很多个person对象。但是这里还有两个麻烦,需要我们注意。

第一个麻烦就是这样处理,我们没有办法识别对象实例的类型。使用instanceof可以识别对象的类型,如下例子:

var obj = {}; var foo = function() {} console.log(obj instanceof Object); // true console.log(foo instanceof Function); // true

1
2
3
4
5
var obj = {};
var foo = function() {}
 
console.log(obj instanceof Object);  // true
console.log(foo instanceof Function); // true

因此在工厂模式的基础上,我们需要使用构造函数的方式来解决这个麻烦。

2. 对象字面量模式

var person = {
    name: "Brittany",
    age: 23,
    job: "web front-end engineer",
    sayName: function() {
        console.log(this.name);
    }
};
person.sayName();

虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为解决这个问题,可以使用工厂模式的一种变体。

var obj = new Base();

  接口继承:只继承方法签名

五、继承

我们常常结合构造函数与原型来创建一个对象。因为构造函数与原型的不同特性,分别解决了我们不同的困扰。因此当我们想要实现继承时,就必须得根据构造函数与原型的不同而采取不同的策略。

我们声明一个Person对象,该对象将作为父级,而子级cPerson将要继承Person的所有属性与方法。

function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; }

1
2
3
4
5
6
7
8
function Person(name, age) {
    this.name = name;
    this.age = age;
}
 
Person.prototype.getName = function() {
    return this.name;
}

首先我们来看构造函数的继承。在上面我们已经理解了构造函数的本质,它其实是在new内部实现的一个复制过程。而我们在继承时想要的,就是想父级构造函数中的操作在子级的构造函数中重现一遍即可。我们可以通过call方法来达到目的。

// 构造函数的继承 function cPerson(name, age, job) { Person.call(this, name, age); this.job = job; }

1
2
3
4
5
// 构造函数的继承
function cPerson(name, age, job) {
    Person.call(this, name, age);
    this.job = job;
}

而原型的继承,则只需要将子级的原型对象设置为父级的一个实例,加入到原型链中即可。

// 继承原型 cPerson.prototype = new Person(name, age); // 添加更多方法 cPerson.prototype.getLive = function() {}

1
2
3
4
5
// 继承原型
cPerson.prototype = new Person(name, age);
 
// 添加更多方法
cPerson.prototype.getLive = function() {}

图片 9

原型链

当然关于继承还有更好的方式,这里就不做深入介绍了,以后有机会再详细解读吧。

1. Object构造函数

var person = new Object();
person.name = "Brittany";
person.age = 23;
person.job = "web front-end engineer";
person.sayName = function() {
    console.log(this.name);
};
person.sayName();   //Brittany

<script type="text/javascript">
    var Browser = function(){};
    Browser.prototype.run = function(){
        alert("I'm Gecko,a kernel of firefox");
    }

 

5)原生对象的原型

所有原生引用类型(Object、Array、String)都在其构造函数的原型上定义了方法,如:Array.prototype.sort()、String.prototype.subString(), 通过原生对象的原型可以取得所有默认方法的引用,并可以定义新方法。

console.log(typeof Array.prototype.sort);        //function
console.log(typeof String.prototype.substring);  //function

String.prototype.startsWith = function(text) {
    return this.indexOf(text) == 0;
};
var msg = "Hello World";
console.log(msg.startsWith("Hello"));           //true

    var box2 = new Box();
    alert(box2.name);//trigkit4,原型的值,没有被box1修改
</script>

  instance instanceof Object  // true

5. 原型模式

var array1 = [1,2,3];

  Person.prototype.sayName = function() {
    console.log(this.name);
  }

4. 构造函数模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    }    
}
var person1 = new Person("Brittany", 23, "Web front-end engineer");
var person2 = new Person("Closure", 26, "Manager");
person1.sayName();
person2.sayName();
console.log(person1.sayName == person2.sayName);   //false

使用构造函数的主要问题:每个方法都要在每个实例上重新创建一遍。如代码中所示,person1的sayName和person2的sayName不相等。可以将函数定义转移到构造函数外部来解决。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    console.log(this.name);
}

sayName函数的定义转移到了构造函数外部。而在构造函数内部,我们将sayName属性设置成等于全局的sayName函数。这样一来,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这的确解决了两个函数做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让让人无法接受的是:如果需要定义很多方法,那就要定义很多个全局函数,于是这个自定义的引用类型就无封装性可言。这些问题可通过使用原型模式来解决。

这里,用构造器Shape()新建了一个实体,然后用它去覆盖该对象的原型。

  function sayName() {
    console.log(this.name);
  }

3)更简洁的原型语法

function Person() {}
Person.prototype = {
    name: "Brittany",
    age: 23,
    job: "Web front-end engineer",
    getName: function() {
        return this.name;
    }
};
var friend = new Person();
console.log(friend instanceof Person);      //true
console.log(friend instanceof Object);      //true
console.log(friend.constructor == Person);  //false
console.log(friend.constructor == Object);  //false

在上面的代码中,将Person.prototype设置为等于一个对象字面量形式创建的新对象,最终结果相同。但有一个例外:constructor属性不再指向Person了。每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。而我们在这里使用的语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。此时尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了。

通过如下方式,将constructor手动设置为合适的值。

Person.prototype = {
    constructor: Person,
    name: "Brittany",
    age: 23,
    job: "Web front-end engineer",
    getName: function() {
        console.log(this.name);
    }
};

再看个数组对象的实例。当我们创建出array1这个对象的时候,array1实际在Javascript引擎中的对象模型如下:

  实现继承:继承实际方法

Object类型是JavaScript中使用最多的一种类型。创建Object实例的方式有多种,接下来一一列举。

复制代码 代码如下:

    xhk.sayName()  // xhk

复制代码 代码如下:

      var p1 = new Person('xhk', 36, '男');  // Person {name: "xhk", age: 36, gender: "男", sayName: ƒ}

1.判断属性是在构造函数的实例里,还是在原型里,可以使用`hasOwnProperty()`函数
2.字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反
为什么指向Object?因为Box.prototype = {};这种写法其实就是创建了一个新对象。
而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性
3.如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的
4.如果是原型方法,那么他们的地址的共享的

  p1.constructor == Object  // true

array1.push(4);

      c. 没有return语句;

在这里person1和person2就是实例,prototype是他们的原型对象。

    2)构造函数模式,创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

Javascript中的对象实例本质上是由一系列的属性组成的,在这些属性中,有一个内部的不可见的特殊属性——__proto__,该属性的值指向该对象实例的原型,一个对象实例只拥有一个唯一的原型。

  function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName = sayName;
  }

再举个栗子:

  p1.name = 'coco';

push这个方法来自于array1的__proto__成员指向对象的一个方法(Array.prototye.push())。正是因为所有的数组对象(通过[]来创建的)都包含有一个指向同一个具有push,reverse等方法对象(Array.prototype)的__proto__成员,才使得这些数组对象可以使用push,reverse等方法。

  Person.prototype.name = 'xhk';
  Person.prototype.age = 36;
  Person.prototype.gender = 'man';

__ptoto__属性

  原型链作为实现继承的主要方法

工厂模式

  var p1 = new Person();

当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。

6. Object.defineProperty(person, name, {

            Person.prototype.printName=function() //原型对象
            {
                alert(this.name);
            }

4. 数据属性: configurable, enumerable, writable, value

<script type="text/javascript">
    function Animal(name)   //积累构造函数
    {
        this.name = name;//设置对象属性
    }

  Object.getOwnPropertyNames(Person.prototype);   // ["constructor", "name", "age", "gender", "sayName"]

1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象
3.没有return 语句

  p1.name  // 'xhk'

ECMAScript中,实现继承的方法就是依靠原型链实现的。

  p1.friends.pop('coco1'); 

    TwoShape.prototype = new Shape();
    Triangle.prototype = new TwoShape();
</script>

  原型与实例之间的关系

复制代码 代码如下:

  在全局作用域中定义的函数实际上只能被某个对象使用。

复制代码 代码如下:

  1. 继承

<script type="text/javascript">
    function Obj(){};

  1)原型链

复制代码 代码如下:

  function Person () {};

    var box1 = new Box();
    alert(box1.name);//Bill,原型里的值
    box1.name = "Lee";
    alert(box1.name);//Lee,就进原则
</script>

  p1.name // 'xhk'

TAG标签:
版权声明:本文由必威发布于必威-前端,转载请注明出处:可以理解A将B中的方法和属性全部克隆了一遍,