js基础(二):构造函数与原型

写在最前:构造函数和原型模式的使用场景很广泛,但因为对概念的混淆不清导致无法熟练掌握。切图带你从代码和流程图一步步攻克,纯干货,建议收藏详看,原型模式理解图非常重要,务必多看几遍!

前往查看demo源码
js基础(一):判断类型

构造函数

构造函数与普通函数区别

  • 构造函数的首字母必须大写,用来区分于普通函数(驼峰命名),此为约定俗成
  • 构造函数内部使用的this对象,来指向即将要生成的实例对象,而普通函数中的this指向调用函数的对象(没有对象时默认为window)
  • 构造函数默认return this,但也可以用return语句,返回值会根据return值的类型而有所不同。普通函数可使用return返回值
  • 构造函数使用New来生成实例对象进行调用,普通函数直接调用
// 构造函数
function Person(name, age) {
  this.name = name
  this.age = age
  this.introduction = function() {
    console.log(`my name is ${this.name}, I'm ${this.age} years old`)
  }
  //return this //构造函数默认有这句
}
var p = new Person('qietuniu', 18) // this=Person
p.introduction()

// 普通函数
function person(name, age) {
  this.name = name
  this.age = age
  this.introduction = function() {
    console.log(`my name is ${this.name}, I'm ${this.age} years old`)
  }
  return `直接返回:我的名字 ${this.name}, 我 ${this.age} 岁`
}
console.log(person('qietuniu', 18)) //this=window
window.introduction()

构造函数内的上下文this指向即将要生成的实例对象Person,普通函数内使用this,指向window时容易造成全局污染。该构造函数将陪着我们读完这篇文章,之后的示例将在这基础上演示!

隐藏的构造函数

  • var a={}是var a=new Object()的语法糖
  • var a=[]是var a=new Array()的语法糖
  • function Person(){}是var Person=new Function()的语法糖

语法糖:更简单表达一个操作的语法,能够增加程序的可读性,在性能上不会带来损失的同时提高开发编码的效率,从而减少程序代码出错的机会!

instanceof

可使用instanceof判断一个函数是否是一个变量的构造函数.

解析:instanceof的判断逻辑是实例p的__proto__一层一层往上,能否对应到Person.prototype,同样也能到Object.prototype.

查看instanceof具体使用>>

new一个对象的过程

  1. 创建一个新对象
  2. this指向这个新对象
  3. 执行代码,即对this赋值
  4. 返回this

解析(以Person函数为例):
1.创建一个新对象p
2.将构造函数Person()中的this指向新创建的对象p
3.p的_proto_(隐式原型)属性指向Person函数的prototype(显示原型),创建构造函数与原型以及对象的关系
4.调用对象,执行Person内属性或方法

原型模式

原型模式理解图

js基础(二):构造函数与原型_第1张图片

  • 构造函数Fn的prototype(显式原型)是原型对象
  • 构造函数可通过new实例化一个实例
  • 原型对象的constructor是构造函数Fn

Person.prototype.constructor = Person

  • 实例的构造函数属性(constructor)指向构造函数

p的__proto__.constructor = Person

  • 实例的__proto__(隐式原型)是原型对象

p的__proto__ =Person.prototype

  • Fn.prototype是对象,它的__proto__是object的prototype

Person.prototype的__proto__ = Object.prototype

  • Object的prototype的__proto__为null

Object.prototype的__proto__ = null
Person.prototype的__proto__的__proto__ = null

熟记该图,万变不离其宗

原型的五大原则(学习原型链的基础)

1. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外)

 var obj = {} 
 obj.a = 100 //自由扩展属性
 var arr = []
 arr.a = 100
 function fn() {}
 fn.a = 100

创建对象的三种方法

// 字面量
var o1 = {
  name: 'o1'
}
var o2 = new Object({
  name: 'o2'
})

// 构造函数
var M = function() {
  this.name = 'o3'
}
var o3 = new M()

// Object.create
var O = {
  name: 'o4'
}
var o4 = Object.create(O)
console.log(o1)
console.log(o2)
console.log(o3)
console.log(o4)

2. 所有的引用类型(数组、对象、函数),都有一个__proto__属性(隐式原型),属性是一个普通的对象

 //隐式原型
 console.log(obj.__proto__)
 console.log(arr.__proto__)
 console.log(fn.__proto__)

3. 所有的函数,都有一个prototype属性(显式原型),属性也是一个普通的对象

//显式原型
console.log(fn.prototype)

4. 所有的引用类型(数组、对象、函数),_proto_属性值指向它的构造函数的“prototype”的值

//_proto_属性值指向它的构造函数的“prototype”的值
console.log(`arr.__proto__ === Array.prototype:${arr.__proto__ === Array.prototype}`)
console.log(`obj.__proto__ === Object.prototype:${obj.__proto__ === Object.prototype}`)
console.log(`fn.__proto__ === Function.prototype:${fn.__proto__ === Function.prototype}`)

5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找

Person.prototype.sayName = function() {
  console.log(`我的名字:${this.name}`)
}
p.introduction()
p.sayName()

执行sayName时的时候,对象p本身没有该方法,会去它的__proto__即它的构造函数的prototype中寻找(p.__proto__或者Person.prototype),于是找到sayName.

原型对象

什么是原型对象
Person这个构造函数的显式原型是一个对象,简称原型对象。Person.prototype就是原型对象。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,即原型对象(Person.prototype)是 构造函数(Person)的一个实例。

Person.prototype = p.proto

原型对象的优点:
可以 让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是 可以将这些信息直接添加到原型对象中,比如下面的sayName方法。

Person.prototype.sayName = function() {
  console.log(`我的名字:${this.name}`)
}

如何查找对象自身的属性

  var item
  for (item in p) {
      // 高级浏览器已经在for in中屏蔽了来自原型的属性
      // 以下的判断可保证程序的健壮性,hasOwnProperty方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
      if (p.hasOwnProperty(item)) {
          // 输出name和printName,没有alerName
          console.log(item)
      }
  }

原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

p.toString()是如何调用的
js基础(二):构造函数与原型_第2张图片
执行toString方法时,p本身没有该方法,p.__proto__也没有,继续往上p.proto.__proto__即Person.prototype.proto,Person.prototype就是普通对象,Person.prototype.proto = Object.prototype,Object中存在toString方法。

原型链图
js基础(二):构造函数与原型_第3张图片
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型。对象的内部指针这么一层一层的查找就是原型链查找,如此层层递进,就构成了实 例与原型的链条,这种链式结构叫做“原型链“。

结语

使用场景

jquery中原型的使用
jQuery.fn.init.prototype = jQuery.fn,将原型方法为什么放在jQuery.fn中,是因为要扩展插件如下面的printQT 方法, 只有$会暴露在window全局变量(太多会造成污染),将插件扩展统一到jQuery.fn.xxx这一个接口方便使用。

var jQuery = function() {
  return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
   constructor: jQuery,
   init: function() {
     this.jquery = "1.9.1";
     return this;
   }
 }
 jQuery.fn.init.prototype = jQuery.fn;
 jQuery.fn.printQT = function() {
   console.log("切图")
   return this;
 }
 window.jQuery = window.$ = jQuery;
 console.log(jQuery().printQT())

其他
除了jquery中的运用,在vue中使用诸如echarts的插件时,我们会使用Vue.prototype.$echarts = echarts,将echarts引入到全局使用,同样自定义方法变量也可以如此使用。

尊重原创,如需转载请注明出处!

你可能感兴趣的:(前端概念,javascript)