js基础(七) 面向对象
# 知识点一 单例模式
- 对象数据类型的作用
- 把描述一个事物(同一个对象)的属性和方法放在一个内存空间下,起到了分组的作用,这样不同事物之间的属性即使属性名相同,相互也不会发生冲突
- 我们把这种分组编写代码的模式叫做 单例模式
var person1 = {
name: 'zhangsan',
age: 18
}
var person2 = {
name: 'lisi',
age: 48
}
2
3
4
5
6
7
8
9
- 在单例模式中我们把
person1
和person2
也叫做 命名空间 - 单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式来进行 模块化开发
- **模块化开发:**对于一个相对来说比较大的项目,需要对人协作的开发,我们一般情况下会根据当前项目的需求划分成几个功能模块,每个人负责一部分,同时开发,最后把每个人的代码进行合并
# 知识点二 工厂模式
单例模式虽然解决了分组的作用,但是不能实现批量的生产,属于手工作业模式
工厂模式:把实现同一件事情的相同代码放到一个函数中,以后如果再想实现这个功能,不需要重新的编写这些代码了,只需要执行当前的函数即可
function create(name, age) {
var obj = {
name: name,
age: age,
write: function() {
console.log(`${this.name} can write js`)
}
}
return obj
}
var p1 = create('p1', 18)
p1.write()
var p2 = create('p2', 28)
p2.write()
2
3
4
5
6
7
8
9
10
11
12
13
14
工厂模式 --> 函数的封装 --> 低耦合高内聚
低耦合高内聚:减少页面中冗余代码,提高代码的重复利用率
js
是一门轻量级的脚本 编程语言,html+css
不属于编程语言,属于标记语言所有的编程语言(
.net 、C#、php、Java、c、c++、vb、vf、oc、......
)都是面向对象开发的 --> 继承、封装、多态
- 继承:子类继承父类的属性和方法
- 多态:当前方法的多种形态,在后台语言中多态包含重写(
Override
)与重载(Overload
)
- 多态:当前方法的多种形态,在后台语言中多态包含重写(
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重载(
overloading
) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同
注意:js
中不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个。
js
中有一个操作类似重载但不是重载,我们可以根据传递参数的不一样,实现不同的功能,例如:
function sum(num) {
if(typeof num === 'undefined')
return 0;
return num
}
sum(100)
sum()
2
3
4
5
6
7
# 知识点三 构造函数模式
# 概述
构造函数模式:目的就是为了创建一个自定义类,并且创建这个类的实例
- 在构造函数模式中
new Fn()
执行,如果Fn
不需要传递参数的话,后面的小括号可以省略 this
的问题:在类中出现的this.xxx = xxx
中的this
都是当前类的实例,而某一个属性值(方法),方法中的this
需要看方法执行的时候,前面是否有.
才能知道this
是谁- 类有普通函数的一面,当函数执行的时候,
var xxx
其实只是当前形成的私有作用域中的私有变量而已,它和我们的当前类的实例没有任何的关系,只有this.xxx = xxx
才相当于给当前类的实例增加私有的属性和方法,才和我们的当前类的实例有关系 - 在构造函数模式中,浏览器会默认的把我们当前类的的实例返回(返回的是一个对象数据类型的值),如果我们手动写了
return
返回
- 在构造函数模式中
- 返回的是一个基本数据类型的值,当前实例是不变的,例如
return 100
,即返回的还是当前类的实例 - 返回的是一个引用数据类型的值,当前的实例会被自己返回的值替换掉,例如
return { name: 'p1' }
,返回的就不是当前类的实例了,而是对象{ name:'p1' }
- 返回的是一个基本数据类型的值,当前实例是不变的,例如
- 检测某一个实例是否属于当前类的实例,用
instanceof
,例如p2 instaceof CreateJsPerson2
--> true,同时p2 instaceof Object
--> true,因为所有的实例都是对象数据类型,而每一个对象数据类型都是Object
这个内置类的一个实例,所以p2
是Object
的一个实例
- 检测某一个实例是否属于当前类的实例,用
构造函数模式和工厂模式的区别
- 执行的时候
- 普通函数执行-->
createJsPerson()
- 构造函数执行-->
new createJsPerson()
,通过new
执行后,我们的createJsPerson
就是一个类了,而函数执行返回值(p1
)就是createJsPerson
这个类的一个实例
- 普通函数执行-->
- 在函数代码执行的时候
- 相同:都是形成一个私有的作用域,然后执行顺序为:形参赋值 --> 预解释 --> 代码自上而下执行(类和普通函数一样,也有普通的一面)
- 不同:
- 在代码执行之前,不用自己手动的创建对象,浏览器会默认的创建一个对象数据类型的值(这个对象其实就是我们当前类的一个实例)
- 接下来代码从上到下执行,以当前的实例为执行的主体(
this
代表当前的实例),然后分别的把属性名和属性值赋值给当前的实例 - 最后浏览器会把默认的创建的实例返回
js
中所有的类都是函数数据类型的,它通过new
执行变成了一个类,但它本是也是一个普通的函数js
中所有的实例都是对象数据类型的在类中给实例增加的属性(
this.xxx
)属于当前实例的私有的属性,实例和实例之间是单独的个体,所有私有的属性不是同一个,即p1
和p2
都是CreateJsPerson2
这个类的实例,所以都拥有write
这个方法,但是不同实例之间的方法是不一样的检测数据类型
typeof
有自己的局限性,不能区分Object
下的对象、数组、正则等instanceof
可以检测当前实例属性哪个类in
检测某个属性是否属于当前对象,例如write in p2
--> true,不管私有的属性还是公有的属性,都可以用in
来检测hasOwnProperty
用来检测某个属性是否为当前对象的私有属性,这个方法只能检测私有属性,例如p2.hasOwnProperty('write')
--> trueisPrototypeOf()
方法测试一个对象是否存在另一个对象的原型链上,语法object1.isPrototypeOf(Object2)
构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,这个过程称实例识别
// 普通函数
function CreateJsPerson(name, age) {
var obj = {}
obj.name = name
obj.age = age
obj.write = function() {
console.log(`${obj.name} can write js`)
}
return obj
}
var p = CreateJsPerson('p', 18)
// 构造函数
function CreateJsPerson2(name, age) {
var num = 100 // 私有变量,和当前类的实例没有任何的关系
// this 是当前类的实例
this.name = name
this.age = age
this.write = function() {
// this 需要看write执行的时候才能知道
console.log(`${this.name} can write js`)
}
}
var p1 = new CreateJsPerson2('p1', 18)
var p2 = new CreateJsPerson2('p2', 28)
p2.write() // 方法中的 this 是 p2,this.name --> p2
var write = p2.write
write() // 方法中的 this 是 window,this.name --> undefined
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
# 手写new
function realizeNew () {
//创建一个新对象
let obj = {};
//获得构造函数
let Constructor = [].shift.call(arguments);
//链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
obj.__proto__ = Constructor.prototype;
//绑定this
let result = Constructor.apply(obj,arguments);
//确保new出来的是一个对象
return typeof result === "object" ? result : obj
}
function myNew(Obj,...args){
var obj = Object.create(Obj.prototype);//使用指定的原型对象及其属性去创建一个新的对象
Obj.apply(obj,args); // 绑定 this 到obj, 设置 obj 的属性
return obj; // 返回实例
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 参考
# 知识点四 原型(链)模式
// 构造函数
function CreateJsPerson(name, age) {
this.name = name
this.age = age
}
CreateJsPerson.prototype.write = function() {
console.log(`${this.name} can write js`)
}
var p1 = new CreateJsPerson('p1', 18)
var p2 = new CreateJsPerson('p2', 28)
console.log(p1.write === p2.write) // true
2
3
4
5
6
7
8
9
10
11
12
原型模式:解决了方法或者属性公有问题,即把实例之间相同的属性和方法提取成公有的属性和方法,想让谁公有就把它放在构造函数的
prototype
属性上,例如:CreateJsPerson.prototype.write
相关概念
- 每一个函数数据类型(普通函数、类)都有一个天生自带的属性
prototype
,它存储的值是一个对象数据类型的值,浏览器默认为其开辟一个 堆内存 - 在
prototype
上浏览器天生给它加了一个属性constructor
(构造函数),属性值是当前函数(类)的本身(只有浏览器默认给prototype开辟的这个堆内存才有constructor属性) - 每一个数据对象类型(普通的对象、实例、
prototype
等)也天生自带一个属性__proto__
,这个属性值是当前实例所属类的原型(prototype
)
- 每一个函数数据类型(普通函数、类)都有一个天生自带的属性
Object
是js
中所有对象数据类型的基类(最顶层的类)
- 实例通过
__proto__
可以向上级查找,不管有多少级总能找到Object
- 在
Object.prototype
上没有__proto__
这个属性
- 实例通过
- 原型链模式
- 通过
对象名.属性名
的方式获取属性值的时候,首先在对象的私有的属性上进行查找,如果私有中存在这个属性,则获取的是私有的属性值;如果私有的没有,则通过__proto__
找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有属性的值;如果原型上也没有,则继续通过原型上的__proto__
继续向上查找,一直找到Object.prototype
为止 - 上述的查找机制就是原型链模式
- 通过
- 注意 在IE浏览器中,我们原型模式也是同样的原理,但是
IE
浏览器怕你通过__proto__
把公有的修改,禁止我们使用__proto__
function Fn() {
this.x = 100;
this.y = 200;
this.sum = function() {}
}
Fn.prototype.getX = function() {
console.log(`${this.x}`)
}
Fn.prototype.getY = function() {
console.log(`${this.y}`)
}
Fn.prototype.sum = function() {}
var f1 = new Fn;
var f2 = new Fn;
console.log(Fn.prototype.constructor === Fn) // true
f1.hasOwnProperty === f1.__proto__.__proto__.hasOwnProperty // true
f1.hasOwnProperty === Object.hasOwnProperty // true
// 修改私有/公有属性/方法
f1.sum = function() {} // 修改私有方法
f1.__proto__.sum = function() {} // // 修改公有方法
Fn.prototype.sum = function() {} // // 修改公有方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 在原型模式中,
this
常用的两种情况
- 在类中出现的
this.xxx
中的this
是当前类的一个实例 - 某一个方法中的
this
,看执行的时候.
前面是谁this
就是谁
- 在类中出现的
- 需要先确定
this
的指向(this
是谁) - 把
this
替换成对应的代码 - 按照原型链查找的机制,一步步的查找结果
- 需要先确定
function Fn() {
this.x = 100;
this.y = 200
this.getY = function() {
console.log(`${this.y}`)
}
}
Fn.prototype = {
constructor: Fn,
y: 300,
getX: function() {
console.log(`${this.x}`)
},
getY: function() {
console.log(`${this.y}`)
}
}
var f = new Fn;
// this是实例f
// 即:console.log(`${this.x}`) --> console.log(`${f.x}`)
// 所以:x 是 100
f.getX()
// this是Fn的原型
// 即:console.log(`${this.x}`) --> console.log(`${f.__proto__.x}`) --> console.log(`${Fn.prototype.x}`)
// 所以:x 是 undefined
f.__proto__.getX()
// this是Fn的原型
// 即:console.log(`${this.y}`) --> console.log(`${f.__proto__.y}`) --> console.log(`${Fn.prototype.y}`)
// 所以:x 是 300
f.__proto__.getY()
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
- 链式编程
- 原理:
sort
是Array.prototype
上的公有的方法,而数组ary
是Array
这个类的一个实例,所以ary
可以使用sort
方法
- 原理:
sort
执行完成的返回值是一个排序后的数组
,可以继续执行reverse
reverse
执行完成的返回值是一个数组,可以继续执行pop
pop
执行完成返回的值是被删除的那个元素,不是一个数组,所以再执行push
报错
Array.prototype.myUnique = function() {
var obj = {}
for(var i = 0; i < this.length; i++) {
var cur = this[i]
if(obj[cur] === cur) {
this[i] = this[this.length - 1]
this.length--
i--
continue
}
obj[cur] = cur
}
obj = null
return this
}
var ary = [12, 23, 23, 13, 12, 13, 23, 13, 12]
ary.myUnique().sort((a, b) => a-b).reverse().pop()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 批量设置公有属性
- 起别名的方式
- 重构原型对象的方式:
- 自己新开辟一个堆内存,存储我们公有的属性和方法,把浏览器原来给
Fn.prototype
开辟的那个替换掉 - 只有浏览器天生给
Fn.prototype
开辟的堆内存里面才有constructor
,而我们自己开辟的这个堆内存没有这个属性,这样constructor
指向就不是Fn
而是Object
,为了和原来的保持一致,我们需要手动的增加constructor
指向 - 用这种方式给内置类增加公有属性,浏览器会屏蔽掉这个修改原型对象的操作,但是我们可以通过
Array.prototype.sort
这种方式修改内置类原型上的原有方法,所以我们以后在内置类的原型上增加方法,命名都需要加特殊的前缀
- 自己新开辟一个堆内存,存储我们公有的属性和方法,把浏览器原来给
function Fn() {
this.x = 100;
this.y = 200;
this.z = 300;
}
var f = new Fn;
// 普通方式设置公有属性
Fn.prototype.getX = function() {
console.log(`${this.x}`)
}
Fn.prototype.getY = function() {
console.log(`${this.y}`)
}
Fn.prototype.getZ = function() {
console.log(`${this.z}`)
}
// 起别名方式设置公有属性
var pro = Fn.prototype
pro.getX = function() {
console.log(`${this.x}`)
}
pro.getY = function() {
console.log(`${this.y}`)
}
pro.getZ = function() {
console.log(`${this.z}`)
}
// 重构原型对象的方式
Fn.prototype = {
constructor: Fn,
a: function() {},
b: function() {},
getX: function() {
console.log(`${this.x}`)
},
getY: function() {
console.log(`${this.y}`)
},
getZ: function() {
console.log(`${this.z}`)
}
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 知识点五 继承
# 方式一 原型继承
常用
原型继承是
js
中最常用的一种继承方式子类
B
想要继承父类A
中的所用的属性和方法(私有+公有),只要让B.prototype = new A
即可特点:它是把父类中私有的+公有的都继承到了子类原型上(子类公有的)
核心:原型继承并不是把父类(
A
)中的属性和方法克隆一份一模一样的给子类(B
),而是让子类(B
)和父类(A
)之间增加了原型链的链接,以后子类的实例(b
)想要父类(A
)中的方法(getX
),需要一级级的向上查找来使用特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
// 父类
function A() {
this.x = 100
}
A.prototype.getX = function() {
console.log(this.x)
}
// 子类
function B() {
this.y = 300
this.x = 200
}
B.prototype = new A;
B.prototype.constructor = B
B.prototype.getY = function() {
console.log(this.y)
}
// 测试
var b = new B
b.getX()
b.getY()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方式二 **借用构造函数继承(**call继承)
把父类私有的属性和方法克隆一份一模一样的作为子类私有的属性
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
特点:
- 解决了原型继承中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
// 父类
function A() {
this.x = 100
}
A.prototype.getX = function() {
console.log(this.x)
}
// 子类
function B() {
A.call(this) // 把A执行让A里面的this变为B的实例即b
this.y = 300
}
B.prototype.getY = function() {
console.log(this.y)
}
var b = new B
console.log(b.x) // 100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 方式三 冒充对象继承(拷贝继承)
核心:把父类私有的+公有的属性和方法克隆一份一模一样的作为子类私有的属性
特点:
- 支持多继承
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用
for in
访问到)
// 父类
function A() {
this.x = 100
}
A.prototype.getX = function() {
console.log(this.x)
}
// 子类
function B() {
this.y = 200
var temp = new A
for (var key in temp) {
this[key] = temp[key]
}
temp = null
}
B.prototype.getY = function() {
console.log(this.y)
}
var b = new B
console.log(b.getX()) // 100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 方式四 组合继承
原型继承+借用构造函数继承(
call
继承)核心:使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承
特点:
- 弥补了借用构造函数继承(
call
继承)的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 - 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
- 弥补了借用构造函数继承(
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
// 父类
function A() {
this.x = 100
}
A.prototype.getX = function() {
console.log(this.x)
}
// 子类
function B() {
A.call(this)
this.y = 300
}
B.prototype = new A
B.prototype.constructor = B
B.prototype.getY = function() {
console.log(this.y)
}
var b = new B
console.log(b.getX()) // 100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 方式五 寄生组合继承
常用
寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
核心:子类构造函数复制父类的自身属性和方法,子类原型只接收父类的原型属性和方法
特点:
- 堪称完美
缺点:
- 实现复杂
// 父类
function A() {
this.x = 100
}
A.prototype.getX = function() {
console.log(this.x)
}
// 子类
function B() {
A.call(this)
this.y = 300
}
function createObject(o) {
function fn() {}
fn.prototype = o
return new fn
}
// B.prototype = Object.create(A.prototype) // IE 8 不兼容
B.prototype = createObject(A.prototype) // IE 8 兼容
B.prototype.constructor = B
B.prototype.getY = function() {
console.log(this.y)
}
var b = new B
console.log(b.getX()) // 100
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
# 方式六 中间类继承
// 例如求平均值,去掉一个最大值,去掉一个最小值
function avgFn() {
console.log(arguments)
arguments.__proto__ = Array.prototype
arguments.sort((a, b) => a - b)
arguments.pop()
arguments.shift()
return (eval(arguments.join('+')) / arguments.length).toFixed(2)
}
console.log(avgFn(10, 20, 30, 10, 30, 40, 40))
2
3
4
5
6
7
8
9
10
# 知识点六 小技巧
# for...in...
for...in...
循环在遍历的时候,默认的会把自己私有的和在它所属类原型上扩展的属性和方法都可以遍历到,但是一般情况下遍历一个对象只需要遍历私有的即可,我们可以使用以下的判断进行处理
Object.prototype.aaa = function() {}
var obj = { name: 'test', age: 18 }
// 不常用
for (var key in obj) {
if (obj.propertyIsEnumerable(key)) {
console.log(key) // name, age
}
}
// 常用
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key) // name, age
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
可以看出 for in
应用于数组循环返回的是数组的下标和数组的属性和原型上的方法和属性,而 for in
应用于对象循环返回的是对象的属性名和原型中的方法和属性。
使用 for in
也可以遍历数组,但是会存在以下问题:
- index索引为字符串型数字,不能直接进行几何运算
- 遍历顺序有可能不是按照实际数组的内部顺序
- 使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性
- for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值
所以for in
更适合遍历对象,不要使用for in
遍历数组。
# for...of..
for..of
适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()
不同的是,它可以正确响应break、continue
和return
语句
for-of
循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用for-in
循环(这也是它的本职工作)或内建的Object.keys()
方法
// 对象
Object.prototype.sayHello = function () {
console.log('Hello');
}
var myObject = {
name: 'zhangsan',
age: 10
}
for (let key of myObject) { // myObject is not iterable
console.log(key);
}
// 数组
Array.prototype.sayHello = function () {
console.log("Hello");
}
var myArray = [1, 200, 3, 400, 100];
for (let key of myArray) {
console.log(key); // 1, 200, 3, 400, 10
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
为Object实现迭代器协议
Object.prototype[Symbol.iterator]=function () {
var index=0;
var arr=[];
for (var item in this){
arr.push(this[item])
}
//迭代器对象
return{
next:function () {
//迭代对象
return{
value:arr[index++],
done:index>arr.length
}
}
}
}
var obj={
name:'Nikki',
age:18,
add:'earth'
}
for(v of obj) {
console.log(v)
}
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
for in 和for of的区别 (opens new window)
# Object.create
Object.create(proObj)
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
,即要把proObj
作为这个对象的原型。
function Fn() {
this.name = 'test'
this.age = 18
}
var obj = {
constructor: Fn,
getName: function() {
console.log(this.name)
}
}
Fn.prototype = obj
var f = new Fn
var newObj = Object.create(f, {
getAge:{
value: function() {
console.log(this.age)
}
}
})
newObj.__proto__ // f
newObj.__proto__.__proto__ // Fn.prototype --> obj
newObj.__proto__.__proto__.__proto__ // Object.prototype
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 手写
Object.create(proObj)
function object(o) {
function Fn() {}
Fn.prototype = o
Fn.prototype.constructor = Fn
return new Fn;
}
var obj = {
getX: function() {
console.log('get x!')
}
}
var newObj = object(obj)
2
3
4
5
6
7
8
9
10
11
12
Object.create (opens new window)
# 知识点七 原型链扩展
# 扩展第一步 在内置类的原型上扩展方法
基于内置类的原型扩展方法注意事项:
- 自己扩展的方法最好加上特殊的前缀,方式把内置的方法覆盖掉
- 链式写法的核心:执行完某一个方法,不仅得到了想要的结果,而且返回值还需要是当前这个类的实例,只有这样才能一直链下去
var ary = [12, 23, 34, 12]
// 原生的实现原理
Array.prototype.pop = function() {
// this --> ary
this.length--
}
// 扩展去重方法
Array.prototype.unique = function unique() {
var obj = {}
for(var i = 0; i < this.length; i++) {
var cur = this[i]
if(cur === obj[cur]) {
this[i] = this[this.length - 1]
this.length--
i--
continue
}
obj[cur] = cur
}
obj = null
// 为了实现链式写法
return this
}
ary.unique()
ary.pop()
console.log(ary) // [12, 23]
ary.__proto__.pop() // this --> ary.__proto__ --> Array.prototype, 方法不起作用
console.log(ary) // [12, 23]
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
31
32
# 扩展第二步 批量给原型上设置公有方法
注意
- 如果之前原型上存在一些方法的话,我们现在新创建的这个对象会把之前写的那些方法都给覆盖掉,所以浏览器内置类禁止自己创建一个新对象来扩展原型的方法的,防止我们用
Fn.prototype = {}
把内置的方法覆盖掉 - 自己创建的对象中不会自带
constructor
属性,所以导致了我们f.constructor
的结果是Object
而不是我们认为的Fn
function Fn() {
this.x = 100
}
Fn.prototype = {
constructor: Fn,
a: function a() {},
b: function b() {},
c: function c() {}
}
var f = new Fn
2
3
4
5
6
7
8
9
10
11
# 扩展第三步 函数也是对象
# 扩展第四步 基于call、apply的原型链深入
# 扩展第五步 更多的继承方式
# 扩展第六步 constructor深入
# 扩展第七步 组合应用
# 知识点八 需要总结的内容
- 把
window
、document
、div
、a
的原型链一级级的结构画出来,画出来后,每一级原型上都有哪些属性和方法以及每个方法的作用、哪些兼容哪些不兼容总结出来。
# 参考
es6中class类的全方面理解 (opens new window)