构造函数原型与原型链

一、构造函数

构造函数的函数名以大写字母为开头。
JavaScript规定,一个函数可以用new关键字来调用。那么此时将按顺序发生四件事情:

(1)隐秘的创建一个新的空对象

(2)将这个函数里面的this绑定到刚才创建隐秘新对象上

(3)执行函数体里面的语句

(4)返回这个新的对象

function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
    console.log(11);//会打印因为第三步执行了函数体内的语句
}
var xiaoming = new People();//11
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}
xiaoming.name;//小明

我们发现People不仅能执行还能通过new返回一个对象,我们就称呼像People这样的函数为构造函数,而xiaoming这样返回出来的对象就称呼为构造函数的实例,(这里就是People的实例)

1.1注意事项

1.1.1如果构造函数内没有this将返回一个空对象。

function People(){
for(let i = 0; i < 3; i++) {
  console.log(i);
  }
}
var xiaoming = new People();//0,1,2
console.log(xiaoming);//{}

1.1.2 构造函数构造函数中,不允许出现return语句

但出现return语句的时候,如果return的是一个对象,那么通过new放回出来的对象就是return出来的对象,如果是简单数据类型,返回正常情况下该返回的实例。

//正常情况
function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
}
var xiaoming = new People();
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}

//return 一个对象
function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
return {a: 1, b: 2}
}
var xiaoming = new People();
console.log(xiaoming);// {a: 1, b: 2}

//return 简单数据类型
function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
return 1
}
var xiaoming = new People();
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}

二原型

原型的特点:
1、原型也是对象,原型是函数对象的一个属性
2、原型自带constructor属性, constructor指定构造函数
3、构造函数创建出的对象会继承原型的属性和方法

每个函数都有原型。

function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
}
People.prototype.sayHello = function() {
console.log('你好');
}
var xiaoming = new People();
console.log(typeof People.prototype);//Object
console.log(People.prototype.constructor);//People
xiaoming.sayHello();//你好

function test(){};
console.log(test.prototype)//{}

2.1实例对象的原型

当一个对象被new出来时,这个实例对象会自带一个_proto_属性,这个属性指向生出这个实例的构造函数的原型。

function People(){
    this.name = "小明";
    this.age = 18;
    this.sex = "男"; 
}
var xiaoming = new People();
console.log(xiaoming.__proto__ === People.prototype)//true

注意:__proto__前后各有两个英文状态下的下划线
我们把构造函数的原型叫做构造函数实例的原型对象。
__proto__属性,是Chrome自己的属性,别的浏览器不兼容,但是别的浏览器也有原型对象,只不过不能通过proto进行访问而已。

三应用

如果我们吧方法定义在构造函数内部时

function People(name,age){
    this.name = name;
    this.age = age;
   this.sayHello = function() {
    console.log('hello')
  }
}
let xiaoming = new People('小明',14);
let xiaogang = new People('小刚', 14);
xiaoming .sayHello;//hello
xiaogang.sayHello;//hello

在new一个xiaoming的时候,构造函数中的代码顺序执行,绑定了name,age,sayHello,new一个xiaogang的时候也是如此,你有没有发现,不同的对象拥有相同他方法.因此可以将复用的方法放在原型对象上.
以避免消耗内存。

function People(name,age){
    this.name = name;
    this.age = age;
}
People.prototype.sayHello = function() {
  console.log('hello')
   }
let xiaoming = new People('小明',14);
let xiaogang = new People('小刚', 14);
xiaoming .sayHello();//hello
xiaogang.sayHello();//hello
console.log(xiaoming.sayHello === xiaogang.sayHello);//true

四常见的创建对象的模式

4.1工厂模式

function test(name,age) {
let o = new Object();
  o.name = name;
  o.age = age;
  o.sayHello = function() {
    console.log('helllo')
   };
return o
};
let test1 = test('first',1);
let test2 = test('second', 2);

⼯⼚模式虽然解决了创建多个相似对象的问题,但没有解决对象标识的问题。

4.2构造函数模式

function Test(name,age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log('helllo')
   };
};
let test1 = test('first',1);
let test2 = test('second', 2);

相对于工厂模式,构造函数模式的优点:
1、没有显式地创建对象

2、直接将属性和方法赋给了 this 对象

3、没有 return 语句

构造函数模式的缺点: 就是每个⽅法都要在每个实例上重新创建⼀遍。

4.3.原型函数模式

function People(){}
People.prototype = {
constructor: People,
 name : 'xiaoming',
 age : 18,
sayHello () {
console.log('hello')
  }

}
let xiaoming = new People();
let xiaogang = new People();

原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),
缺点:就是省略了为构造函数传递初始值参数,导致所有的实例对象都是相同的属性和方法,其主要问题在于,如果原型上存在引用类型的值的属性时:

function People(){}
People.prototype = {
constructor: People,
 name : 'xiaoming',
 age : 18,
sayHello () {
console.log('hello')
  },
friend : ['a']

}
let xiaoming = new People();
let xiaogang = new People();
xiaoming.friend.push('b');
console.log(xiaoming.friend);//['a', 'b']
console.log(xiaogang.friend);//['a', 'b']

4.4、混合模式(构造函数模式+原型模式)

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性

function People(name,age){
this.name = name;
this.age = age

}
People.prototype = {
constructor: People,
sayHello () {
console.log('hello')
  }

}
let xiaoming = new People('小明',1);
let xiaogang = new People('小刚',2);

五原型链

只要是对象,一定有原型对象(除了Object.prototype),而一个实例通过__proto__属性可以知道它的原型对象,也就是它的构造函数的原型。而它的原型对象也是对象,那么它的原型对象应该也有原型对象。

function People() {};
let people = new People;
console.log(people.__proto__ );
console.log(people.__proto__ .__proto__.constructor);//Object
console.log(people.__proto__ .__proto__.__proto__);//null

Object是一个函数,是系统内置的构造函数,用于创造对象的。Object.prototype是所有对象的原型链终点。

所以,当我们在一个对象上打点调用某个方法的时候,系统会沿着原型链去寻找它的定义,一直找到Object.prototype。

function People() {};
let people = new People;
Object.prototype.sayHello = function() {
console.log('hello')
};
people.sayHello();

Object.prototype是所有对象的原型链的终点,所以我们直接给Object.prototype增加一个方法,那么所有的对象都能调用这个方法.

5.1引用类型的构造函数

我们可以利用原型链的机制,给数组对象增加方法.

所有的引用类型值,都有内置构造函数。比如

new Object()

new Array()

new Function()

new RegExp()

new Date();
以数组为例子;

let arr = [1,2,3,45,6];
console.log(arr.__proto__ === Array.prototype)//true
console.log(arr.__proto__.__proto__.constructor === Object)//true
let arr = [1,2,3,45,6];
let max = -Infinity;
arr.__proto__.max = function() {
for(let i = 0; i < arr.length; i++) {
      if(arr[i] >= max) {
      max = arr[i]
    }
  }
  return max
}
arr.max()//45

5.2基本数据类型的包装类

基本类型值,也有包装类型。所谓包装类型,就是它的构造函数。
1、new Number()
2、new String()
3、new Boolean()

let num = 10; 
console.log(num.__proto__ === Number.prototype)//true
console.log(num.__proto__.__proto__.constructor === Object)//true

let str= '10'; 
console.log(str.__proto__ === String.prototype)//true
console.log(str.__proto__.__proto__.constructor === Object)//true

let bol= true; 
console.log(bol.__proto__ === Boolean.prototype)//true
console.log(bol.__proto__.__proto__.constructor === Object)//true

let un= undefined; 
console.log(un.__proto__.constructor)//报错

let nu= null; 
console.log(nu.__proto__.constructor)//报错

六对象与属性

6.1打点调用

var obj = {
    a : 1,
    b : 2,
    c : 3
}
obj.__proto__ = {
    d : 4
}


console.log(obj.a); //1
console.log(obj.b); //2
console.log(obj.c); //3
console.log(obj.d); //4

6.2in

var obj = {
    a : 1,
    b : 2,
    c : false
}
obj.__proto__ = {
    d: 20
}
console.log("a" in obj);    //true
console.log("b" in obj);    //true
console.log("c" in obj);    //true
console.log("d" in obj);    //true

in和打点调用,如果自己或者只要在原型链上有这个方法就会被查找到并返回true。

通过for...in循环调用可枚举的属性。

var obj = {
    a : 1,
    b : 2,
    c : false
}
obj.__proto__ = {
    d: 20
}
for( let k in obj) {
console.log(k)//a,b,c,d.
}

6.2 hasOwnProperty

这个方法定义在了Object.prototype对象上面,所以任何一个Object都能够拥有这个方法。

var obj = {
    a : 1,
    b : 2,
    c : 3
}
obj.__proto__ = {
    d : 4
}

console.log(obj.hasOwnProperty("a")); //true
console.log(obj.hasOwnProperty("b")); //true
console.log(obj.hasOwnProperty("c")); //true
console.log(obj.hasOwnProperty("d")); //false,在原型链上不在实例上

把自己身上的属性输出:

for(let k in obj){
    obj.hasOwnProperty(k) && console.log(k);
}

把不在自己身上的属性输出:

var obj = {
    a : 1,
    b : 2,
    c : 3
}
obj.__proto__ = {
    d : 4
}

for(let k in obj){
if(!obj.hasOwnProperty(k) && k in obj) {
  console.log(k)//d
  }

}

6.3 定义多个属性Object.defineProperties()

    var test = {};
    Object.defineProperties(test, {
        _name: {
            value: 'chen'
       },
        name: {
            get: function() {
                return this._name;
            },
            set: function(newvalue) {
                if (typeof newvalue != 'string') {
                    throw new Error(TypeError);
                } else {
                    this._name = newvalue;
                }
            }
        }
    })
console.log(test.name);//chen
test.name = 10;//typeerror;
test.name = 'aa';
console.log(test.name);//aa

读取属性的描述对象Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor(test, 'name')
//{enumerable: false, configurable: false, get: ƒ, set: ƒ}

6.4 instanceof

function Boy(){};
function Girl(){};
Girl.prototype = new Boy();
let xiaohong = new Girl();
console.log(xiaohong.constructor)//Boy;
console.log(xiaohong instanceof(Boy))//true
console.log(xiaohong instanceof(Girl))//true

首先,实例会继承原型对象的属性和方法(当然包括constructor属性);这里Girl.prototype上的constructor本来应该指向Girl,但由于
Girl.prototype = new Boy(),new Boy()实例继承了Boy.prototype上的constructor:Boy,最后new Girl()也继承了它原型对象new Boy()的constructor。所以xiaohong.constructor为Boy。

instanceof 运算符的机理: 遍访xiaohong这个对象的原型链上的每个原型对象,如果遍访到这个原型对象,是某个构造函数的prototype,那么就认为xiaohong是这个构造函数的实例,返回true。

6.5检测数据类型

Object.prototype.toString.call(123);  //"[object Number]"
Object.prototype.toString.call("123");  //"[object String]"
Object.prototype.toString.call(true);  //"[object Boolean]"
Object.prototype.toString.call(undefined);  //"[object Undefined]"
Object.prototype.toString.call(null);  //"[object Null]"
Object.prototype.toString.call(function(){});  //"[object Function]"
Object.prototype.toString.call([]);  //"[object Array]"
Object.prototype.toString.call({});  //"[object Object]"

七 继承

7.1原型链继承

简单来说就是把父类的实例作为子类的原型。

function People(name){
    this.name = name;
}
People.prototype.sayHello = function(){
    alert("你好我是" + this.name);
}
function Student(name,xuehao){
    this.name = name;
    this.xuehao = xuehao;
}
Student.prototype = new People('大明');
var xiaohong = new Student("小红",1001);

这样子类既可以继承父类的属性、方法又可以在自己追加方法。
不过当父类原型上具有引用类型值的属性时,会有大问题:

function People(name){
    this.name = name;
}
People.prototype.arr= [1,2,3]
function Student(name,xuehao){
    this.name = name;
    this.xuehao = xuehao;
}
Student.prototype = new People('大明');
var xiaohong = new Student("小红",1001);


//**注意这里**
xiaohong.arr.push(4);
console.log(xiaohong.arr);//[1,2,3,4]
var xiaoming = new Student("小明",1001);
console.log(xiaoming.arr);//[1,2,3,4]

7.2构造函数继承

function People(name){
    this.name = name;
}
People.prototype.sayHello = function(){
    alert("你好我是" + this.name);
}

function Student(name,xuehao){
//核心
    People.call(this,name);
    this.xuehao = xuehao;
}
Student.prototype.study = function(){
    alert("好好学习,天天向上");
}

var xiaohong = new Student("小红",1001);

之前说过,用call先执行函数,然后在执行函数的时候改变this指向,
那么 People.call(this,name),也就好理解了。
缺点,所有的属性和方法只能定义在构造函数身上,无法实现函数的复用,子类也不能访问到父类的原型。

7.3. 组合继承

就是将原型链继承和构造函数继承组合在一起;继承两个优点。

function People(name){
    this.name = name;
}
function Student(name,xuehao){
//构造继承
    People.call(this,name);
    this.xuehao = xuehao;
}

//核心语句,
//原型继承
Student.prototype = new People('大明');

Student.prototype.study = function(){
    alert("好好学习,天天向上");
}

var xiaohong = new Student("小红",1001);

xiaohong.sayHello();

缺点:
调用了两次父类构造函数,一次是在创建子类原型时,一次是在子类构造函数中。

7.4. 寄生组合继承

function People(name){
    this.name = name;
}
function Student(name,xuehao){
    People.call(this,name);
    this.xuehao = xuehao;
}


function Fn() {};
Fn.prototype = People.prototype
Student.prototype = new Fn();//核心语句,

Student.prototype.study = function(){
    alert("好好学习,天天向上");
}

var xiaohong = new Student("小红",1001);

xiaohong.sayHello();

这里只调用了一次People构造函数,避免了组合继承的缺点。

7.5. 圣杯模式

圣杯模式就是将借用的构造函数封装一下

function inherit(People,Student){
    function Fn(){}
    Fn.prototype = People.prototype
    Student.prototype = new Fn();
    Student.prototype.constructor = Student;//增强对象
}
inherit(People,Student )
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容