引言
对于面向对象,相信大家一定不陌生。最近看了一些关于es6面向对象的知识,正好通过这篇文章把关于面向对象的东西给串起来分享给大家。
什么是对象
很多人会鄙视我,说你这篇文章是骗骗刚入行的小朋友的吧,什么是对象我还能不知道?骂我的吃瓜群众先冷静一下,你可能对对象一无所知。
{
name: '李小花',
sayname () {
console.log(this.name)
}
}
这是我们最常见的对象,这个对象是通过对象字面量形式创建的。
对象的含义是无序的集合,其属性可以包含基本值、对象或者函数。
属性类型
js中有两种内置的属性,数据属性和访问器属性,这两个属性是只有内部才能访问的属性,所以这些属性都放在了两对方括号中,如[[enumerable]],大家在vue中经常
数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性。
[[Configurable]]:表示能否通过 delete
删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。[[Enumerable]]:表示能否通过 for-in 循环返回属性。
[[Writable]]:表示能否修改属性的值。
[[Value]]:包含这个属性的值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为
undefined。
现在有一个对象通过字面量创建
var person = {
name: '张全蛋'
}
[[Configurable]]、[[Enumerable]]、[[Writable]]属性都会被设置为true,[[Value]]被设置为了‘张全蛋’。如果想修改这几个属性任意一个值,必须使用大名鼎鼎的Object.defineProperty()方法,为啥说它大名鼎鼎,因为如果你接触过vue,就知道他核型就是通过这个方法实现的。
var person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: '张全蛋'
})
Object {name: "张全蛋"}
现在的name属性是只读的,如果是严格模式的话,
这样做还会报错。同样的也适用于其他属性,我这里就不一一演示了。
注意⚠️,Object.defineProperty()方法只有现代浏览器才支持,IE8只是部分实现。
访问器属性
访问器属性不包含数据值,它们包含一对 getter 和 setter 函数(这两个函数都不是必须的)。在读取访问器属性时,会调用
getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter
并传入新值,这个函数负责决定如何处理数据。访问器属性有如下 4 个特性。
[[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
[[Enumerable]]:表示能否通过 for-in 循环返回属性。
[[Get]]:在读取属性时调用的函数。默认值为 undefined。
[[Set]]:在写入属性时调用的函数。默认值为 undefined。
访问只能通过bject.defineProperty()方法来定义。
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, 'year', {
get: function() {
return this._year;
},
set: function(newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); // 2
alert(book.year); // 2005
访问器属性 year 则包含一个 getter 函数和一个 setter 函数。getter 函数返回 _year 的值,setter 函数通过计算来确定正确的版本。因此,把 year 属性修改为 2005 会导致 _year 变成 2005,而 edition 变为 2。这是使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化。
注意⚠️,访问器属性只有IE9以上才支持,这就是为什么VUE只能支持到IE9的原因。
创建对象
js面向对象第一步是什么?答:创建对象。创建对象有很多中方式,我们最常用的是对象字面量来创建对象,var obj = {}
,你看我这不就创建了一个对象了吗,我还干嘛要继续了解那些奇葩的方法呢?这么想的人活该单身,多掌握些找对象只有好处没有坏处哈。正经的,高阶上有这么一句话,使用对象字面量创建单个对象,有个明显的缺点,使用同一个接口创建很多对象,会产生大量重复的代码。为了解决这个问题,我们需要了解下面?这些方式。
工厂模式
工厂模式很简单,贴上一段代码。
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name)
}
return o;
}
var person1 = createPerson("铁蛋", 20, '工头')
var person2 = createPerson("李四", 30, '挖掘机驾驶员')
工厂模式的优点不必多说,如果我想创建两个对象,上面同样有name、age、job属性,这样就省去了创建包含同样属性多个对象的麻烦,但是却没有解决对象识别的问题。
有人会问,对象识别是什么鬼。我们创建对象是为了模仿类的概念,这里的person1,person2应该都属于“人”一类,但是显然我们现在没办法将他们归为一类,所以这个时候逼格更高的方法出现了。
构造函数模式
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person("铁蛋", 20, '工头')
var person2 = new Person("李四", 30, '挖掘机驾驶员')
这里有个显然很突出的地方,就是这个Person的P是大写的,其实大写不是必须的,据说这种习惯是很多后端程序员转前端带过来的。构造函数模式跟工厂模式不一样的地方还在于,没有用new Object显式地创建对象,同样没有return语句。
那我们在new完一个构造函数,实则产生一个实例,我们new一个构造函数,会经历以下神奇的四步。
创建对象 将this指向这个新对象 为这个对象添加属性 返回这个对象
person1、person2 是我们通过 new Person这个构造函数得到的,所以这两个的构造函数都是Person,constructor(构造函数)属性就都是Person,我以前一直都不能理解constructor是什么东西,现在才理解原来constructor的中文翻译就是构造函数?,也难怪我英文最熟的一句就是"hello kugou"了。我们可以通过使用instanceof操作符来检测对象的类型。
let arr = new Array(2)
arr instanceof Array // true
arr instanceof Object // true
构造函数优于工厂模式也是在于它可以通过instanceof辨识出一类的对象。
接下来大家看一段一般没品的面试官会考的问题
this.name = "张全蛋"
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person("铁蛋", 20, '工头')
var person2 = Person("李小花", 30, "厂花")
person1.sayName() // 铁蛋
person2.sayName() // 报错
我们首先要确定一个概念,构造函数也是函数,如果不用new 的方式来调用它,它跟普通函数没有半毛钱的区别,我们知道在函数的作用域是window,所以this指向的是window,所以这段代码person2对象this就是window,window没有sayName属性,所以会报错。如果通过的是new方式调用的话,我们上面也讲了,为将this赋值给这个对象,所以this就是person1这个实例。那么构造函数是不是没有缺点呢?显然是不对的,因为我已经这么问了。构造函数的缺点,每个方法都要在实例上重新创建一遍,js中函数也是对象,定义函数就是实例化对象
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function () {
alert(this.name)
}
}
每次new一个function就会多一次标识符解析,标识符(通常指命名)的解析是有代价的,实际上没有那种计算机操作可以不产生性能开销。在执行环境的作用域链(扯到作用域链就一定会扯到闭包问题,以后有空再仔细聊聊闭包)中,一个标识符所在的位置越深,它的读写速度也就越慢。也就是说函数中读写局部变量总是最快的,而读写全局变量总是最慢的。因为全局变量总是在执行环境作用域的末端。其实我们可以将函数移出来当全局函数来处理,但那样会造成全局函数污染,这里就不多做介绍。