# 九.数据分类[Function]
# 1.原型
每个对象初始化时会生成一个默认的属性,这个属性就是原型,当在这个对象上查找某个属性时,会先在这个对象本身查找这个属性,没有找到就会去原型上查找,原型内部有原型,没有找到依次向内部找属性,这样形成的链状结构我们称为原型链,原型链的顶层是 null
函数原型上的一些方法
- apply: function()
- arguments: TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context.
- bind: function()
- call: function()
- caller: TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context.
- constructor: function()
- length: 0
- name: ""
- toString: function()
- Symbol(Symbol.hasInstance): function()
- 1.对象
对象 Object 即是对象也是函数,访问函数的原型(prototype) 得到一个对象 Object.prototype,这个对象的原型(__proto__)可以访问到对象的根 null;访问对象的原型(__proto__)可以得到一个对象 Function.prototype,访问这个对象的原型(__proto__)可以得到 Object.prototype
实例化对象({})是一个对象,他的原型(__proto__)指向 Object 函数的 prototype,即 Object.prototype
- 2.函数
函数 Function 即是一个对象也是一个函数,访问函数原型(prototype)得到一个对象 Function.prototype,这个对象的原型(__proto__)指向 Object.prototype;Function 对象的原型(__proto__)指向 Function.prototype
- 3.自定义函数
函数 Function 即是一个对象也是一个函数,访问函数原型(prototype)得到一个对象 Function.prototype,这个对象的原型(__proto__)指向 Object.prototype;Function 对象的原型(__proto__)指向 Function.prototype
# 1.1 apply
- 模拟 apply
Function.prototype.apply = function (context, args) {
context = context ? Object(context) : window
context.fn = this
if (!args) {
return context.fn()
}
//利用数组的toString的特性(字符串和数组拼接)
let r = eval("context.fn(" + args + ")")
delete context.fn
return r
}
2
3
4
5
6
7
8
9
10
11
fn1.apply("hello", [1, 2, 3, 4])
# 1.2 arguments
函数中参数的传递方式
首先看一道题目
function test(person) {
person.age = 26
person = {
name: "yyy",
age: 30,
}
return person
}
const p1 = {
name: "yck",
age: 25,
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 对于以上的结果你知道吗?
- 首先,函数传参是传递对象指针的副本
- 然后 p1 的值被修改了
- 但是当我们重新为 person 分配了一个对象时就出现了分歧
- 这是 person 指针会指向重新赋值的值,所以可以知道 js 中函数赋值操作是共享赋值
# 1.3 bind
bind 实现原理
1.bind 方法可以绑定 this 指向 绑定参数
2.bind 方法返回一个绑定后的函数(高阶函数)
3.如果绑定的函数被 new 了 当前函数的 this 就是当前的实例
4.new 出来的结果可以找到原有类的原型
Function.prototype.bind = function (context) {
let that = this
let bindArgs = Array.prototype.slice.call(arguments, 1)
function Fn() {}
function fBound() {
let args = Array.prototype.slice.call(arguments)
return that.apply(this instanceof fBound ? this : context, bindArgs)
}
Fn.prototype = this.prototype
fBound.prototype = new Fn()
return fBound
}
fn.prototype.flag = "动物类"
let bindFn = fn.bind(obj, "猫")
let instance = new bindFn(9)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1.4 call
var obj = { name: "123" }
function fn() {
console.log(this)
}
fn() //-->this-->window
fn.call(obj)
Function.prototype.myCall = function (context) {
//1、将fn中的this变成obj
eval(this.toString().replace("this", context))
//2、让fn方法执行
}
fn.myCall(obj)
2
3
4
5
6
7
8
9
10
11
12
13
/* 思考题 */
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
fn1.call(fn2) //-->1
fn1.call.call(fn2) //fn1.call 首先通过原型链找到Function.prototype上的call方法,然后再让call方法通过原型找到Function原型上的call(因为call本身的值也是一个函数,所以可以找到Function.prototype),在第二次找到call的时候让方法执行,方法中的this
fn.call() //-->window 严格模式下window
fn.call(null) //-->window 严格模式下null
fn.call(undefined) //-->window 严格模式下undefined
//apply和call方法的作用是一模一样的,都是用来改变方法的this关键字并且把方法执行:而且在严格模式下和非严格模式下对于第一个参数是null/underfined这种情况的规律也是一样的
fn.call(obj, 100, 200)
fn.apply(obj, [100, 200]) //-->call在给fn传递参数的时候,是一个个的传递值得,而apply不是一个个传,而是把要给fn传递的参数统一的放在一个数组中进行操作--》但是也相当于一个个的给fn形参赋值
//bind:这个方法在ie6-8下不兼容-->和call/apply类似都是用来改变this关键字的
//预处理:事先把fn的this改变为我们想要的结果,并且把对应的参数值也准备好,以后要用到了,直接的执行即可
fn.call(obj, 1, 2) //改变this和执行fn函数是一起都完成了
var tempFn = fn.bind(obj, 1, 2) //只是改变了fn中的this为obj,并且给fn传递了两个参数值1、2,但是此时并没有把fn这个函数执行,执行bind会有一个返回值,这个返回值tempFnv就是我们把fn的this改变后的那个结果
tempFn()
/* 获取数组的最大值 */
var ary = [12, 32, 11, 3, 43, 5, 12, 23, 43]
//1
var sum = ary.sort(function (a, b) {
return a - b
})
var min = sum[0]
var max = sum[sum.length - 1]
//2
var min = ary[0]
var max = ary[0]
for (var i = 1; i < ary.length; i++) {
ary[i] < min ? (min = ary[i]) : null
ary[i] > max ? (max = ary[i]) : null
}
//3
var min = eval("Math.min(" + ary.toString() + ")")
var max = eval("Math.max(" + ary.toString() + ")")
//4
var max = Math.max.apply(null, ary)
var min = Math.min.apply(null, ary)
//6-->括号表达式
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
;(fn1, fn2)()(
//只有fn2执行了 (x1,x2,x3)括号表达式,一个括号中出现多项内容,中间用“,”隔开,但是我们最后获取到的结果只有最后一项
fn2,
obj.fn
)()(
//执行的是obj.fn,但是执行的时候里面的this变为了window而不是obj
obj.fn
)() //this还是obj
/* 获取平均数 */
function avgFn() {
//将类数组转换为数组
// var ary = []
// for(var i = 0;i<arguments.length;i++){
// ary[ary.length] = arguments[i]
// }
// ary.sort(function(a,b){
// return a-b
// })
var ary = Array.prototype.slice.call(arguments)
//或者ary = [].slice.apply(arguments)
}
ary.pop()
ary.unshift()
return (eval(ary.join("+")) / ary.length).toFixed(2)
//2种方法
function avgFn() {
Array.prototype.sort.call(arguments, function (a, b) {
return a - b
})
;[].shift.call(arguments)
;[].pop.call(arguments)
return eval([].join.call(arguments, "+") / arguments.length).toFixed(2)
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
- 模拟 call
call 的特点:
- 可以改变我们当前函数的 this 指向
- 会让当前函数执行
Function.prototype.call = function (context) {
context = context ? Object(context) : window
context.fn = this
let args = []
for (let i = 1; i < arguments.length; i++) {
args.push("arguments[" + i + "]")
}
//利用数组的toString的特性(字符串和数组拼接)
let r = eval("context.fn(" + args + ")")
delete context.fn
return r
}
2
3
4
5
6
7
8
9
10
11
12
调用
fn1.call(fn2)
多个 call
//多个call会指向window
fn1.call.call.call(fn2)
2
new
的底层实现
function _new(fn, ...args) {
let obj = Object.create(fn.prototype)
fn.call(obj)
return obj
}
2
3
4
5
# 1.5 caller
# 1.6 constructor
# 1.7 length
# 1.8 name
# 1.9 toString
# 1.10 Symbol
# 2.原型链
JS 中的继承
JavaScript 常用的八种继承
# 2.继承
# 1.原型链继承
构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function() {
return this.parentName + 1
}
function Child() {
this.childName = "child"
}
Child.prototype = new Parent() // 关键部分,需要先将子构造函数的指针指向父构造函数的实例
Child.prototype.getChildName = function() {
return this.childName + 1
}
var child = new Child()
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
优点
- 简单易实现,父类新增的实例与属性子类都能访问
缺点
- 多个实例对引用类型的操作会被篡改
- 无法实现多继承
- 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数后面
- 创建子类实例时,不能向父类构造函数中传递参数
场景
- vue 源码中构造子组件
# 2.构造函数继承
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function() {
return this.parentName + 1
}
function Child() {
this.childName = "child"
Parent.call(this)
}
Child.prototype.getChildName = function() {
return this.childName + 1
}
var child = new Child()
console.log(child.parentName) //parent
console.log(child.getParentName()) //child.getParentName is not a function
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
优点
- 可以实现多继承(call 或者 apply 多个父类)
- 解决了子类构造函数向父类构造函数中传递参数
缺点
- 只能继承父类的实例属性,不能继承原型属性
- 无法实现复用,每个子类都有父类构造函数的副本,影响性能
# 3.组合继承
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function () {
return this.parentName + 1
}
function Child() {
this.childName = "child"
Parent.call(this)
}
Child.prototype = new Parent()
Child.prototype.getChildName = function () {
return this.childName + 1
}
var child = new Child()
Child.prototype.constructor = Child
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
优点
- 可以继承父类原型上的属性,可以传参,可复用
- 每个新实例引入的构造函数属性是私有的
缺点
- 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数
# 4.原型式继承
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function () {
return this.parentName + 1
}
function object(obj) {
function F() {}
F.prototype = obj
return new F()
}
var Child = object(new Parent())
Child.childName = "child"
Child.getChildName = function () {
return this.childName + 1
}
var child = Child
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 缺点:原型链继承多个实列的引用类型属性执行相同,存在篡改的可能。
# 5.寄生式继承
在原型式继承的基础上,增强对象,返回构造函数
function createAnother(original) {
var clone = Object(original)
clone.childName = "child"
clone.getChildName = function () {
return this.childName + 1
}
return clone // 返回这个对象
}
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function () {
return this.parentName + 1
}
var Child = createAnother(new Parent())
var child = Child
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.寄生组合式继承
function inheritPrototype(c, p) {
var prototype = Object.create(p.prototype)
prototype.constructor = c
c.prototype = prototype
}
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function () {
return this.parentName + 1
}
function Child() {
this.childName = "child"
Parent.call(this)
}
// 将父类原型指向子类
inheritPrototype(Child, Parent)
Child.prototype.getChildName = function () {
return this.childName + 1
}
var child = new Child()
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 7.混入方式继承
function Parent() {
this.parentName = "parent"
}
Parent.prototype.getParentName = function () {
return this.parentName + 1
}
function Child() {
this.childName = "child"
}
Child.prototype.getChildName = function () {
return this.childName + 1
}
function MyClass() {
Parent.call(this)
Child.call(this)
}
MyClass.prototype = Object.create(Parent.prototype)
Object.assign(MyClass.prototype, Child.prototype)
MyClass.prototype.contructor = MyClass
var child = new MyClass()
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 8.ES6 类继承 extends
class Parent {
constructor() {
this.parentName = "parent"
}
getParentName() {
return this.parentName + 1
}
}
class Child extends Parent {
constructor() {
super()
this.childName = "child"
}
getChildName() {
return this.childName + 1
}
}
var child = new Child()
console.log(child.parentName) //parent
console.log(child.getParentName()) //parent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
← 八.数据分类[Array] 十.数据判断 →