在js中,一对{}
其实就是一个对象vue
var person = { name: "tom", age: 23, read: function () { console.log(name, ": read book") } }
经过系统的构造函数建立一个空的对象,而后用js动态语言的特性,若是一个对象没有某个属性或者方法,那么咱们点一下再附上值就行了程序员
var person2 = new Object() person2.name = "jerry" person2.age = 23 person2.say = function () { console.log(person2.name, ": say hello") }
自定义构造方法通常都是首字母大写的函数浏览器
function Person(name, age, sex) { this.name = name this.age = age this.sex = sex this.say = function () { console.log(this.name, " :say hello") } } // 建立对象时,使用 new 关键字 p = new Person("tom", 23, "man") console.log(p instanceof Person)
自定义的构造方法建立对象,会经历以下几个步骤函数
function Person(name,age,sex) { // new Object 做为当前的返回值 var obj = new Object() obj.name = name obj.age = age obj.sex = sex obj.say = function () { console.log(this.name," :say hello") } // 手动将对象返回出去 return obj } // 工厂模式建立对象,不须要使用new 关键字 var p = Person("tom",23,"man") console.log(p instanceof Person) // false
看下面的例子:this
// 构造函数和实例的关系 function Person(name) { this.name = name this.say = function () { console.log(this.name," :say hello") } } // 对象p是经过 自定义的构造函数Person建立出来的 var p = new Person("tom") console.dir(p) console.dir(Person)
打印的结果以下:prototype
__proto__
属性中有constructor属性,上面记录着本身的构造方法。p是Person类型
。__prototype__实际上就是原型对象,在下文中会详细的说code
仍是上面的例子,看以下的输出也就能理解了对象
console.log(p.constructor === Person) // true console.log(p.__proto__.constructor == Person) // true console.log(p.__proto__.constructor == Person.prototype.constructor) // true // 由此推断出,p === Person console.log(p instanceof Person) // true
其实有个小问题,看上面代码的第一行console.log(p.constructor === Person)
咱们经过上面的代码也看不到实例对象p constructor属性啊,怎么就能用,也不报错undefined呢?blog
其实这就是牵扯到js对象的原型链了,(下面的章节会说),总的来讲,就是js的对象会优先使用构造方法中的属性和方法,若是构造函数中不存在咱们使用的属性和方法的话,就尝试去这个对象所对应的构造方法中的原型对象中的属性和方法,再没有就会报错。事件
为何会忽然再来看js的原型呢?
由于看到了vue的源码中,大量的方法都被添加再vm的原型上,因此,回顾一下原型确定是躲不过去了。
通常咱们使用原型就是为了节省空间。
想理解节省了什么空间? 那就看看下面这个不节省空间的例子。
// 构造函数建立对象带来的问题 function Person(name) { this.name = name this.say = function () { console.log(this.name,": say hello") } } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say() // tom : say hello p2.say() // jerry : say hello // todo 返回false, 表示说,p1和p2的say方法,并非同一份, 其实这并非一件好事 console.log(p1.say == p2.say)
上面的p1 和 p2 都是经过一个构造函数建立出来的不一样对象,他们里面都有say这个函数,当咱们输出 p1.say == p2.say
时,返回了false,说明每一个对象中都有一份say方法,那假设有1000个对象,岂不是就有1000个say方法了? 这确定是浪费空间的。
那么有没有办法可让每次new出来的对象都使用一份say方法呢?
固然,以下:
// 共享函数,引出原型 function Say() { console.log(this.name, ": say hellp") } function Person(name) { this.name = name this.say = Say } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say()// tom : say hellp p2.say()// jerry : say hellp // 这样的话,确实能实现节省空间,可是容易出问题 console.log(p1.say == p2.say) // ture
如今确实实现了咱们的需求,可是不够优雅,并且统一出现问题,js是动态类型的语言,那咱们像下面这样,假设不知道已经有Say这个函数了,而后将var Say = "hello"
放置在第Say函数以后,就会产生覆盖。
看下的例子:咱们往构造方法的原型对象上添加一个say方法。
其实这块也不是很差理解,你想啊,js的对象经过构造方法建立出来,咱们把公共的方法,属性放在构造方法的原型对象中,是否是就可让他们共享这些方法和属性呢?
function Person(name) { this.name = name } // 在原型上添加方法 // 为何能够说原型是对象呢? 想一想js中一个对象能够经过 点 , 动态点添加属性和方法? Person.prototype.say = function () { console.log(this.name,":say hello") } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say()//tom :say hello p2.say()//jerry :say hello console.log(p1.say == p2.say) // true
经过console.dir()
打印下上面的实例对象和构造函数,获得以下图:
console.dir(p1) console.dir(p2) console.dir(Person)
经过上图能够看到,能够获得下面的结论:
__proto__
, 这个属性是用来给浏览器使用的,而不是给程序员使用,因此咱们称它为非标准属性。 此外谷歌浏览器是支持这个属性的,可是在IE8浏览器中,咱们执行这句console.log(p1.__proto__)
会报错,说undefined实例对象是经过 new 构造函数建立出来的,因此构造函数是建立实例对象的模版。
构造函数就是那个首字母大写的函数,在js里面咱们能直接console.log(构造函数)
由于这个构造函数其实也是个对象。
原型的做用咱们说了,就是为了将公共的方法抽取出来,所有存放在构造函数的原型对象中,而实现数据的共享,节省内存。
实例对象的·__proto__
, 是个非标准属性,也是个对象,这个对象指向了 构造方法的prototype
属性。
构造方法的 prototype
属性是个标准属性, 同时也是个对象,咱们对经过 构造方法.prototype.属性/方法 = XXX
的方式为其添加属性和方法。
咱们经过 对象.属性/方法
时, 会优先从对象的构造方法中查找,若是找不到的会再尝试从原型中查找,这也是为何会出现一个明明没有为一个对象添加相应的属性或者方法可是对象却能点出来,而且能正常使用。固然若是原型中也不存在的话,就会报错说 undefined
下面出现的this并不难理解, 就是咱们new 出来的对象自己
function Person(name) { // 考虑一下,这个this是谁? this.name = name console.log(this) } var p = new Person("tom")
咱们在构造方法的原型对象上添加一个方法say,在这say方法中使用的this对象指的一样是 咱们new 出来的对象自己。即方法的调用者。
function Person(name) { // 考虑一下,这个this是谁? this.name = name console.log("n10: ",this) } Person.prototype.say = function () { // todo 这里的this指的是谁呢? // 首先,方法是添加在原型对象上, 那么this指的是原型对象吗? // 经过控制台能够看到,this.name ,其实不是原型对象,而是say()方法的调用者(实例对象) console.log("n16: ",this.name,": say hello") } var p1 = new Person("tom") var p2 = new Person("jerry") p1.say() p2.say()
下面在给构造方法的原型对象添加方法时,不只出现了this, 还出现了that。
this对象依然是咱们手动new出来的对象自己。
that一样是指向了咱们new出来的对象自己,之因此须要中转一下,是由于在按钮的点击事件里面,this指向的是按钮自己。
// 用面向对象的方式封装构造函数 function ChangeStyle(btnId, dvId, color) { this.btnObj = document.getElementById(btnId) this.dv = document.getElementById(dvId) this.color = color } // 在构造方法的原型上添加方法 ChangeStyle.prototype.init = function () { // 这里面的this表示的是 调用init方法的实例对象 var that = this this.btnObj.onclick = function () { // todo 为何原型中的函数中,就不能使用this,而是that呢??? // todo 或者问下,当前函数中的this是谁呢? that.dv.style.backgroundColor = that.color } }
function Person(name) { this.name = name } // 前面的例子中咱们都是像下面这样写代码, 这实际上是对原来的 原型对象属性的累加 // 原来的原型对象中有个属性,叫作consturctor Person.prototype.say = function(){ //todo }
这样设置原型的话,其实是对原来的原型对象的覆盖,因此说须要像下面这样从新添加constructor的指向。
固然我也试了一下,若是说覆盖原来的原型对象,且不添加contructor的指向,咱们使用 instanceof 判断实例对象是不是对应的构造函数类型时,仍是能获得正确的结果。
Person.prototype = { constructor:Person, // 手动修改构造器的指向 height:"20", weight:"20", say:function () { // todo } }
function Person(name) { this.name = name this.say = function () { console.log("say") // 经过这个例子,能够看到,对象的方法中能够直接调用对象的方法 this.eat() } this.eat = function () { console.log("eat") } }
function Person(name) { this.name = name } Person.prototype.say = function(){ console.log("say") // 原型中的方法也能够相互访问 this.eat() } Person.prototype.eat = function(){ console.log("eat") } var p1 = new Person("tom") p1.say()
像这样就能够实现对原型中的方法进行覆盖的操做。
固然能够经过在原型上添加方法实现对原有封装类的拓展。
// 在现有的js封装类上干这件事,也算是在修改源码 String.prototype.myReverse = function () { for (var i = 0; i < this.length; i++) { console.log("发生倒叙") } } var str = "123" str.myReverse()