面向对象
# 面向对象
# new、this
# new
# 对象是什么
- 对象是单个事物的抽象
- 对象是一个容器,封装了属性(property)和方法(method)
- 属性是对象的状态,方法是对象的行为(完成某种任务)
构造函数
- 对于JS来讲,对象体系是基于构造函数(constructor)和原型链(prototype)
let Salvatore = function() {
this.status = '心态永远年轻'
}
2
3
从上可以看出,构造函数就是一个普通函数,但是有自己的特征和用法,Salvatore就是构造函数,为了与普通函数区别,构造函数名字的第一个字母通常大写。构造函数的特点有两个。
- 函数体内部的this关键字,代表了所要生成的对象实例(也可以说是,谁来new它他就是指向谁,关于this的问题马上就会细细了解的)
- 生成对象的时候,必须使用new命令
# 如何使用new
let Salvatore = function() {
this.status = '心态永远年轻'
}
let xuanliao = new Salvatore()
xuanliao.status // '心态永远年轻'
// 再对象内部使用局部的严格模式,如果完了使用new命令直接调用回报错
function Salvatore (name,age){
'use strict'
this._name = name
this._age = age
}
Salvatore() // error
// 或者利用new的target判断是否是new的对象
function Salvatore (name,age) {
if (new.target === Salvatore) {
return new Error('请使用new命令声明对象!')
}
this._name = name
this._age = age
}
// 上面的方法是给出报错信息,让开发者能够察觉,一下还有一种办法解决new的问题
function Salvatore (name,age) {
if (!(this instanceof Salvatore)) { // 在用! || 这样的运算符的时候一定看好范围,是否需要括号
return new Salvatore(name,age)
}
this._name = name
this._age = age
}
Salvatore('xuanliao', 25) // 可以正常使用
new Salvatore('xuanliao', 25) // 也能够使用
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# new命令的原理
- 使用new命令时,4步走
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的prototype属性。
- 将这个空对象赋值给函数内部的this关键字。
- 开始执行构造函数内部的代码。
function _new(constructor, params) {
let args = [].slice.call(arguments) // 将对象转化成数组
let constructor = args.shift() // 将构造函数取出 shift回改变数组
let context = Object.create(constructor.prototype) // 创造一个空对象,继承构造函数的prototype的属性
let result = constructor.apply(context, args)
return (typyof result === 'object' && result != null) ? result : context
}
let actore = _new(Salvatore,'xuanliao',25)
2
3
4
5
6
7
8
- 对于四步走我的理解是:先生成一个空对象,然后把关于所有this的操作,属性,方法,全部挂在空对象上面,也就是把构造函数的this指向空对象
- 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
let Salvatore = function () {
this.price = 1000;
return 1000;
};
(new Salvatore()) === 1000 // false
// 想起分装vue中的Axios中的return new Promise()
2
3
4
5
6
# this
# this的涵义
- this在有些场景可以理解为“当前”的意思
let obj1 = {
name: 'salvatore',
say(){
console.log(this.name)
}
}
let obj2 = {
name:'xuanliao'
}
obj2.say = obj1.say
obj2.say() // xuanliao // 此时say方法的this指向的是当前obj2这个对象
2
3
4
5
6
7
8
9
10
11
let obj1 = {
name: 'salvatore',
say(){
console.log(this.name)
}
}
let func = obj1.say
func() // undefine this---->全局对象,但全局对象上面没有name的属性
2
3
4
5
6
7
8
- 在JS中,万物皆对象,运行环境也是对象,所以函数都是在某个对象之中运行的,而this就是函数执行时所在的环境,如果只是这样的,是不会让你我觉得云里雾里的,主要是JS的运行环境是动态切换的,导致this的指向是动态的,不可以在刚开始的时候就确定this的指向,在ES6中有了箭头函数就好了,那样就会比较好理解了
# 绑定this的方法
- call,this,bind,都是改变this的指针,说几个应用场景
<!-- call -->
<!-- 判断type的类型,比typeof,instanceof更精确 -->
Object.prototype.toString.call(type).match(/\[object (.*?)\]/)[1].toLowerCase()
<!-- js实现简易版forEach -->
Array.prototype._forEach = function (a, b) {
for (let i = 0; i < this.length; i++) {
<!-- 把上下文指向传进来的b -->
a.call(b, this[i], i, this)
}
}
let a = [1, 1, 2, 3, 3, 4, 5]
a._forEach(function (item, index, arr) {
console.log(index, '---->', item, arr)
console.log(this)
}, 33)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- apply -->
<!-- 找到arr里面的最大数 最小数同理 -->
Math.max.apply(null,arr)
<!-- 利用Array.prototype.concat.apply([],arr) 多维数组降维 -->
function down(arr){
return isMulti(arr) ? down(Array.prototype.concat.apply([],arr)) : arr
}
function isMulti (arr){
let result = false
arr.forEach(item => {
if(isArray(item)){
result = true
}
})
return result
}
function isArray(type){
return Object.prototype.toString.call(type).match(/\[object (.*?)\]/)[1].toLowerCase() === 'array'
}
<!-- 利用apply将类数组对象转为数组 -->
<!-- 非常典型的将arguments转化成数组方便使用 -->
let args = Array.prototype.slice.apply(arguments)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 用bind(绑定)比较少
现在来看也不能说比较少了,在preact/react的项目中,在锁定this的时候主要还是用的这个方法(当然可以使用箭头函数去解决this指针不稳定的问题)
let counter = {
count: 0,
inc(){
this.count++
}
}
let func = counter.inc.bind(counter)
<!-- 如果不使用bind,在调用的时候func显而易见的this指向全局,使用bind之后不管在任何地方调用func,this都指向counter -->
func()
counter.count === 1
<!-- bind也可以传入多个参数(锁定传参) -->
function add (x, y) {
return x * this.m + y * this.m
}
let obj = {
n: 5,
m: 5
}
let add2 = add.bind(obj, 5)
add2(5) === 20
<!-- 将add中的x锁定值为5,再次调用add2的时候只需要传y即可 -->
<!-- bind与call连用 -->
let push = Function.prototype.call.bind(Array.prototype.push)
push([1,2,3,4],5)
<!-- 把call的this指向Array.prototype.push,用同样的原理apply会好理解很多 -->
let push = Function.prototypy.apply.bind(Array.prototype.push)
push([1,2,3,4],[5])
<!-- 两个的返回值一样,就是把push的第一个参数就是this,第二个参数就是要push进去的值 -->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 原型链、继承
# 原型链
在js中,所有对象都有自己的原型对象(prototype)
- 任何一个对象都可以充当其他对象的原型对象
- 由于原型对象也是对象,所有他也又有自己的原型对象,因此就有了原型链的说法(prototype chain)
这样一层一层的往上找,总得有个头,这个头就是Object.prototype,也就是说所有对象都是继承了Object.prototype的属性,这就是所有对象都有valueOf和toString等的方法的原型,这些鬼方法都是从Object.prototype上面带过去的,那么Object.prototype这哥们的原型是什么呢?是null,null没有任何的属性和方法,也没有原型,所以原型链的尽头是null
Object.getPrototypeOf(Object.prototype)
// null
2
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
如果对象自身和它的原型都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”
# constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数 (这就是为什么每次给prototype赋完值之后还需要把prototype.constructor重新回去自身的构造函数,就是为了保证这一点)
function P() {}
let p = new P();
p.constructor === P // true
P.prototype.constructor === P && p.constructor = P.prototype.constructor // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
2
3
4
5
6
7
constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。
function Person(name) {
this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
method: function () {}
};
Person.prototype.constructor === Person // flase
Person.prototype.constructor === Object // true
// 优化的写法
Person.prototype = {
constructor:Person
method: function () {}
};
// 简洁并优化
Person.prototype.method = function(){
// dosomething
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 继承
让一个构造函数继承另一个继承函数,这个非常常见,可以分成两步实现
- 在子类的构造函数中调用父类的构造函数
function Super(name,age){
this.name = name
this.age = age
this.method = function(){
// dosomething
}
}
function Sub(value,name,age){
// 将父类自身的方法和属性拿过来
Super.call(this,name,age)
this.value = value
}
2
3
4
5
6
7
8
9
10
11
12
13
- 让子类的原型指向父类的原型,这样子类就可以继承父类的原型了
// 将父类上面的方法和属性也全部拿过来
Sub.prototype = Object.create(Super.prototype)
// 下面的这种写法
Sub.prototype = new Super()
// 将构造函数指向自身,防止之后的调用出现错误
Sub.prototype.constructor = Sub
2
3
4
5
6
根据以上两个的集合可以得出一种继承的写法是:
function Suber(name, age) {
this.name = name
this.age = age
this.method = function () {
console.log('this is suber method')
}
}
Suber.prototype.color = '#333333'
Suber.prototype.sayHello = function () {
console.log('hi Hello')
}
// 先拿到自身属性和方法
function Sub(name, age, value) {
Suber.call(this, name, age)
this.value = value
}
// 再拿原型的属性和方法
Sub.prototype = Suber.prototype
Sub.prototype.constructor = Sub
let s = new Sub('xuanliao', 27, 'nick')
console.log('---------->', s.name, s.age, s.value, s.color)
s.sayHello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 多重继承
function S1(){}
function S2(){}
function M(){
S1.call(this)
S2.call(this)
}
M.prototype = Object.create(S1.prototype)
Object.assign(M.prototype, S2.prootype)
M.prototype.constructor = M
2
3
4
5
6
7
8
9