首先我们要明白什么是对象?
我也是瞎扯!随便看吧!
“万物皆对象“
一个人,一座房子,一座高山,大海,等等都是对象,
而js
对象就是是一个容器,封装了属性(property)和方法(method)通俗点就是一个男人,有手、脚、头、**、眼睛、鼻子!----【属性(property)】
看到一个漂亮女人,**就会想要**!那怎么去**呢?就要用到【方法(method)】
那么面向对象编程就是 根据一个男人的架构,知道他有手有脚有**,如何去繁衍后代生儿育女,首先需要通过嘴巴搭讪,搭讪完需要赚钱买房,结婚等等方法!
而你就是上帝,每个人都单独去给他们找对象是不可能的,优化和创建一套方法进行封装,就是我们说的面向对象编程了!
说到这里让我回想那个纯真的年代,哪有这么复杂,jquery一统天下,前端的世界如此纯洁无瑕。 时光荏苒,岁月如梭,转眼多年过去。前端天下已各分东西!
面向对象的特性:
封装性
继承性
[多态性]抽象
程序中面向对象的基本体现
在 JavaScript 中,所有数据类型都可以视为对象,然后我们就可以自定义对象。
假如我们要给一个25岁的男人小王
找一个妹子,
我们先给他找一个妹子
var mz = new Object()//创建新妹子 mz.name = '小丽' mz.age = 18 mz.whatyouname = function () { console.log(`姓名${this.name},年龄${this.age}`) }
每次都要新建对象!太麻烦了!简化下
var mz = { name: '小丽', age: 18, whatyouname: function () { console.log(`姓名${this.name},年龄${this.age}`) } }
但是我们面向对象程序设计思想,就可以理解为,mz是一个对象,name和age就是对象的属性(Property)!而我们要把妹子展示出来筛选,就需要先创建一个妹子的对象!然后轻轻的和妹子说:“你三维身高体重多少“!,如果她没有打你(报错),就能自己打印出来了!
但是小王对小丽没感觉,想换一个,这时候弊端就来了!
var mz = { name: '小丽', age: 18, whatyouname: function () { console.log(`姓名${this.name},年龄${this.age}`) } } var mz1 = { name: '小可爱', age: 17, whatyouname: function () { console.log(`姓名${this.name},年龄${this.age}`) } } ....mz100...
我们可以看到,这样的话,如果100个妹子,我们得被弄死!我们来优化下!
function Wenmeizi (name, age) { return { name : name, age : age, whatyouname : function() { console.log(`姓名${this.name},年龄${this.age}`) } } }
然后生成实例
var mz1=Wenmeizi('小丽', 25); var mz2=Wenmeizi('小w', 21);
然后根据妹子说的把妹子数据展示出来
mz1.whatyouname();//姓名小丽,年龄25 mz2.whatyouname();//姓名小w,年龄21
至此!我们基本完成了!但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
这时候我们需要用到构造函数了!
构造函数
内容引导:
构造函数语法
分析构造函数
构造函数和实例对象的关系
实例的 constructor 属性
instanceof 操作符
普通函数调用和构造函数调用的区别
构造函数的返回值
构造函数的问题
我们来看代码
function Wenmeizi(name, age) { this.name = name this.age = age this.whatyouname = function() { console.log(`姓名${this.name},年龄${this.age}`) } } var mz1 = new Wenmeizi('小丽', 18) mz1.whatyouname() // => 姓名小丽,年龄18 var mz2 = new Wenmeizi('小王', 18) mz2.whatyouname() // => 姓名小王,年龄18
这时候我们就可以识别对象的具体类型了,在每一个实例对象中同时有一个 constructor
属性,该属性指向创建该实例的构造函数:
console.log(mz1.constructor === Wenmeizi) // => true
对象的 constructor
属性最初是用来标识对象类型的, 但是,如果要检测对象的类型,还是使用 instanceof
操作符更可靠一些:
console.log(mz1 instanceof Wenmeizi)// => true
总结:
构造函数是根据具体的事物抽象出来的抽象模板
实例对象是根据抽象的构造函数模板得到的具体实例对象
每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数
注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到
可以通过实例的 constructor 属性判断实例和构造函数之间的关系
注意:这种方式不严谨,推荐使用 instanceof 操作符,后面学原型会解释为什么
优化
我们要知道妹子是需要温柔的对待的!那么我们如何温柔呢!我们再来看下代码
function Wenmeizi(name, age) { this.name = name this.age = age this.whatyouname = function() { console.log(`姓名${this.name},年龄${this.age}`) } } var mz1 = new Wenmeizi('小丽', 18) mz1.whatyouname() // => 姓名小丽,年龄18 var mz2 = new Wenmeizi('小王', 18) mz2.whatyouname() // => 姓名小王,年龄18
我们先执行下!
然后我们对比2个对象, 那就是对于每一个实例对象,type 和 whatyouname 都是一模一样的内容, 每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。
输入对比下
console.log(mz1.whatyouname===mz2.whatyouname) // // => false
我们可以看见是false,那么我们可以把函数放到外部!
function whatyouname () { console.log(`姓名${this.name},年龄${this.age}`) } function Wenmeizi(name, age) { this.name = name this.age = age this.whatyouname = whatyouname } var mz1 = new Wenmeizi('小丽', 18) mz1.whatyouname() // => 姓名小丽,年龄18 var mz2 = new Wenmeizi('小王', 18) mz2.whatyouname() // => 姓名小王,年龄18
我们再来执行下
console.log(mz1.whatyouname===mz2.whatyouname) // // => true
就这样我们解决了内存浪费的问题,接下来我们看再有什么优化方法吧!
原型
说明:
使用 prototype 原型对象解决构造函数的问题
分析 构造函数、prototype 原型对象、实例对象 三者之间的关系
属性成员搜索原则:原型链
实例对象读写原型对象中的成员
原型对象的简写形式
原生对象的原型
Object
Array
String
...
原型对象的问题
构造的函数和原型对象使用建议
JavaScript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的所拥有。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype
对象上。
function Wenmeizi(name, age) { this.name = name this.age = age } Wenmeizi.prototype.whatyouname = function() { console.log(`姓名${this.name},年龄${this.age}`) } var mz1 = new Wenmeizi('小丽', 18) mz1.whatyouname() // => 姓名小丽,年龄18 var mz2 = new Wenmeizi('小王', 18) mz2.whatyouname() // => 姓名小王,年龄18
这时所有实例的 type 属性和 whatyouname()方法, 其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。
构造函数、实例、原型三者之间的关系
//任何函数都具有一个 prototype 属性,该属性是一个对象。 function F () {} console.log(F.prototype) // => object F.prototype.sayHi = function () { console.log('hi!') } //构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。 console.log(F.prototype.constructor === F) // => true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype
对象的指针 __proto__
。
var a = new F() console.log(a.__proto__ === F.prototype) // => true
实例对象可以直接访问原型对象成员。
a.sayHi() // => hi!
这其实就构成了整个原型链!
a.sayHi本身是没有带sayHi的,没有就会往上找到上级的 prototype如果没有找到,就会一直往上找,直到返回undefined
总结:
任何函数都具有一个 prototype 属性,该属性是一个对象
构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__
所有实例都直接或间接继承了原型对象的成员
更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype
。 为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
function Person (name, age) { this.name = name this.age = age } Person.prototype = { type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '岁了') } }
在该示例中,我们将 Person.prototype
重置到了一个新的对象。 这样做的好处就是为 Person.prototype
添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor
成员。
所以,我们为了保持 constructor
的指向正确,建议的写法是:
function Person (name, age) { this.name = name this.age = age } Person.prototype = { constructor: Person, // => 手动将 constructor 指向正确的构造函数 type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '岁了') } }
class 类 ES6语法
上面构造函数用的是es5的语法,es6新增了class类
那么我们根据上面的进行es6写法
class Wenmeizi { constructor(name, age) { this.name = name this.age = age } whatyouname() { console.log(`姓名${this.name},年龄${this.age}`) } } var mz1 = new Wenmeizi('小丽', 18) mz1.whatyouname() // => 姓名小丽,年龄18 var mz2 = new Wenmeizi('小王', 18) mz2.whatyouname() // => 姓名小王,年龄18
区别:ES6新增1.class类,用于定义属性和属性值的关键词 ;2.constructor构造器,在构造器中定义实例化对象的属性和属性值,与ES5语法形式完全相同,只是书写格式位置不同。