共计 4471 个字符,预计需要花费 12 分钟才能阅读完成。
写在最前:构造函数和原型模式的使用场景很广泛,但因为对概念的混淆不清导致无法熟练掌握。切图带你从代码和流程图一步步攻克,纯干货,建议收藏详看,原型模式理解图非常重要,务必多看几遍!
前往查看 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 一个对象的过程
创建一个新对象
this 指向这个新对象
执行代码,即对 this 赋值
返回 this
解析(以 Person 函数为例):1. 创建一个新对象 p2. 将构造函数 Person() 中的 this 指向新创建的对象 p3.p 的_proto_(隐式原型)属性指向 Person 函数的 prototype(显示原型),创建构造函数与原型以及对象的关系 4. 调用对象,执行 Person 内属性或方法
原型模式
原型模式理解图
构造函数 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__ = nullPerson.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() 是如何调用的
执行 toString 方法时,p 本身没有该方法,p.__proto__也没有,继续往上 p.__proto__.__proto__即 Person.prototype.__proto__,Person.prototype 就是普通对象,Person.prototype.__proto__ = Object.prototype,Object 中存在 toString 方法。
原型链图
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型。对象的内部指针这么一层一层的查找就是原型链查找,如此层层递进,就构成了实 例与原型的链条,这种链式结构叫做“原型链“。
使用场景
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 引入到全局使用,同样自定义方法变量也可以如此使用。
尊重原创,如需转载请注明出处!