精读JavaScript模式(三),new一个构造函数究竟发生了什么?

1、前言html

上个月底,爸爸由于事故忽然离世,说内心话,如今看到'去世','爸爸'这样的字眼,眼泪都会忍不住在眼眶打转,仍是须要时间治愈。最近也只是零碎的看了下东西,始终沉不下心去读书,直到今天仍是决定捡起以前看的JS模式。函数

前面两篇博客大概记录了书中前两章节我以为一些好用的知识,从这篇开始就是第三章--直接量和构造函数了,难度也不算大,最近下班了在公司花点时间慢慢写。测试

从第三篇开始,我想在介绍每一个知识点前,先以概要的形式将这一部分介绍的东西以问题的形式列出来,方便你们带着问题去读,读完了再回头看问题,看本身能不能答出来,这样也方便检验本身对于知识的掌握状况。this

2、对象直接量spa

概要:什么是对象,什么是对象直接量写法,空对象不是真正的空对象prototype

咱们能够将js中的对象简单理解为名值对组成的散列表,其中值都是名的属性,值能够是原始值(string,number...),也能够是对象,而当对象是函数时,咱们通常称之为方法。code

js中自定义的对象任什么时候候都是可变的,内置本地对象的属性也是能够修改的,好比你能够先建立一个空对象,而后在给它添加一些属性或方法。而在建立对象时,对象直接量写法是较为理想的方式。htm

var me = {};
me.name = "echo";
me.getName = function () {
  return me.name;
};

你能够删除对象的某个属性或方法对象

delete me.name;

其实咱们建立一个对象也不必像上面先建立空对象,再一步步添加属性方式,在对象建立时能够同时把你须要定义的属性同时添加好。blog

let me = {
  name = "echo",
  getName = function () {
    return this.name;
  }
};

那么这种直接用 = 建立对象的方式就是对象直接量的写法,很直接不是吗。对象直接量语法包括:

• 将对象主体包含在一对花括号内。
• 对象内的属性或方法之间使用逗号分隔。最后一个名值对后也能够有逗号,但 在IE下会报错,因此尽可能不要在最后一个属性或方法后加逗号。
• 属性名和值之间使用冒号分隔
• 若是将对象赋值给一个变量,不要忘了在右括号以后补上分号

我在上方提到的空对象能够说是算是一种简称,它们并非真正的空对象,即使申明一个{},它也会从Object.prototype继承不少属性和方法,可是咱们其实有方法建立严格意义上的空对象,我在上一篇文章中就列出了两种方法,有兴趣能够去看看。

3、经过构造函数建立对象

概要:为何推荐对象直接量写法而不是构造函数写法

尽管JS中没有类的概念,但当你想快速建立多个具备共同特征的实例时仍是可使用构造函数,JS中内置了很多构造函数,例如Object(),Date(),String()等等。

咱们用构造函数的写法来创造上面的对象:

var me = new Object();
me.name = "echo";
me.getName = function () {
  return me.name;
}

相比之下,对象直接量的写法与构造函数写法相比代码更少,推荐直接量写法的还有两个缘由

一是它能够强调对象是一个简单的可变的散列表,而没必要必定派生自某个类。

二是当你使用Object()建立对象时,解析器须要顺着做用域链开始查找,直到找到Object构造函数为止,而直接量的写法是不会存在做用域解析行为。

 4、自定义构造函数

概要:当你new一个构造函数时发生了什么?

除了对象直接量和内置构造函数以外,咱们还能够经过自定义的构造函数来建立实例对象,像这样。

var Person = function () {
  this.name = "echo";
  this.sayName = function () {
    console.log('my name is '+ this.name);
  };
}
var me = new Person();
me.sayName();//my name is echo

说个小插曲,这里我自定义的构造函数名Person的字母P其实能够小写,但咱们都知道,内置构造函数都是大写开头,因此为了让构造函数更为醒目,推荐首字母大写!

很奇怪对不对,咱们new一个构造函数获得一个实例,这个实例就继承了构造函数的属性方法,那new这个过程当中到底发生了什么?

1.建立一个空对象,将它的引用赋给this,继承函数的原型。

2.经过this将属性和方法添加至这个对象。

3.最后返回this指向的新对象。

咱们用代码模拟这三句话,就像这样:

var Person = function () {
  // var this = {};
  this.name = "echo";
  this.sayName = function () {
    console.log('my name is '+ this.name);
  };
  // return this; 这里隐性返回的其实就是上面建立的空对象,这个空对象被赋予了name属性和一个sayName方法
}
var me = new Person();
me.sayName();//my name is echo

 在这段代码中,sayName()方法被添加到了this中,但有个问题,无论咱们执行几回new Person(),sayName()方法会反复的被添加到this中,且每次sayName()方法都会在内存中新开内存。

当咱们全部实例中的sayName()方法都是如出一辙时,这种作法是很浪费内存的,推荐作法是将sayName()方法添加在Person的原型中。

var Person = function () {
  this.name = "echo";
};
Person.prototype.sayName = function () {
  console.log('my name is '+ this.name);
};
var me = new Person();
me.sayName();//my name is echo

关于new一个构造函数到底发生了什么,我在前面说会隐性的新建一个空对象赋予this,仍是那句话,这里的空对象并非严格意义上的空,它仍是继承了Person的原型,准确来讲应该是这样。

// var this = Object.create(Person.prototype);

 5、构造函数的返回值

概要:构造函数能返回什么?默认返回什么?

当咱们new一个构造函数老是会返回一个对象,默认返回this所指向的对象。若是咱们没有在构造函数内为this赋予任何属性,则会返回一个集成了构造函数原型,没有本身属性的'空对象'。(若是读不懂这句话,请结合new发生的过程去理解)

尽管咱们没在构造函数内写return语句,也会隐性的返回this,但其实咱们能够返回自定义的对象。像这样:

var Person = function () {
  this.name = "echo";
  var that = {};
  that.name = "wl";
  return that;
};
var me = new Person();
me.name;//wl

构造函数能够返回任意对象,只要你返回的是个对象。假设你返回的不是对象,程序也不会报错,但这个返回值会被忽略,最终仍是隐性的返回this所指向的对象。

var Person = function () {
  this.name = "echo";
  var name = "wl";
  return name;
};
var me = new Person();
me.name; //echo

6、强制使用new 的模式

概要:不使用new调用构造函数会怎样?构造函数内能自定义对象吗?不使用new也能继承构造函数原型的作法

构造函数与普通函数无异,只是调用须要使用new,当咱们不使用new调用时,语法也不会出错,但函数中的this会指向全局对象(非严格模式是window)。

var Person = function () {
  this.name = "echo";
};
var me = Person();
window.name//echo

在这段代码中实际上建立了一个全局对象属性name,你能够经过window.name访问到它。那么说到这里对于构造函数咱们强调两点。

1.构造函数名首字母大写

2.调用构造函数使用new

遵照这些约定确定是好的,但在实际开发的构造函数中,咱们经常看看使用that等其它字面量代替this的作法。这么作的目的是为了确保构造函数按照本身定义的方式执行,而不存建立空对象赋予this等隐性不可见的行为,更可预测。

var Person = function () {
  var that = {};
  that.name = "echo";
  return that;
};
var me = new Person();
me.name;//echo

对于上述代码中,咱们使用that代替了this,使用that只是一种命名约定,你可使用self,me甚至任意非js语言保留字的字段。

或者that都不建立,直接返回一个对象。

var Person = function () {
  return {
    name : "echo"
  };
};
var me = new Person();
var you = Person();
me.name//echo
you.name//echo

这种写法无论咱们是否使用new去调用,都能获得一个实例,但这种模式丢失了原型,全部的实例都不会继承Person()原型上的属性。

var Person = function () {
  return {
    name:'echo'
  }
};
Person.prototype.sayName = function () {
  console.log(1);
};

me.sayName();//报错,自定义对象未指向Person,没继承Person的方法
you.sayName();//报错,同上

咱们在前面说,不使用new时,this指向window(非严格模式),没法继承Person的任何属性。

var Person = function () {
  this.name = "echo"
};
Person.prototype.sayName = function () {
  console.log(1);
};
var me = new Person();
var you = Person();
me.name;//echo
you.name;//报错,此时的name是window的属性
me.sayName();//1
you.sayName();//报错,在实例化过程当中this指向window,并未继承Person的方法

那有办法可让不使用new状况下实例也能继承Person属性的作法吗,固然有,好比调用自身的构造函数:

var Person = function () {
  if(!(this instanceof Person)){
    return new Person();
  }
  this.name = "echo"
};
Person.prototype.sayName = function () {
  console.log(1);
};
var me = new Person();
var you = Person();
me.name;//echo
you.name;//echo
me.sayName();//1
you.sayName();//1

看到没,没使用new的实例you也继承了Person的属性和方法,若是看不懂那应该是对于instanceof运算符不太了解,这里顺带说下

instanceof运算符用于测试构造函数的prototype属性是否出如今对象的原型链中的任何位置---MDN

object instanceof constructor   object:要检测的对象.    constructor:某个构造函数

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);//true
console.log(auto instanceof Object);//true

那么上述代码中,new Person()好理解,this就是隐性返回的实例,this instanceof Person为true跳过判断,直接走new一个构造函数时发生的过程,获得实例天然会继承Person的属性和方法。

Person()呢,this指向window,很明显this instanceof Person为false,假假为真,执行判断内的代码new Person();同理,也走了new过程的三部曲,获得的实例也继承了Person的属性和方法。

有疑问或者错误也欢迎你们指出来。

最近老是以为没什么值得开心的事情,趁着双十二,给本身换了个键盘,毕竟天天都是要写代码的,也算换一个心情。

尽管除了吃饭交房租就没什么开销,仍是挺心疼的。

若是你们对于new过程还有疑惑,以及如何实现一个new方法,欢迎阅读博主这篇文章 js new一个对象的过程,实现一个简单的new方法

那么就先写到这里了。

相关文章
相关标签/搜索