# 九.数据分类[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
}
1
2
3
4
5
6
7
8
9
10
11
fn1.apply("hello", [1, 2, 3, 4])
1

# 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) // -> ?
1
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)
1
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)
1
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)
}
1
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
}
1
2
3
4
5
6
7
8
9
10
11
12

调用

fn1.call(fn2)
1

多个 call

//多个call会指向window
fn1.call.call.call(fn2)
1
2

new的底层实现

function _new(fn, ...args) {
  let obj = Object.create(fn.prototype)
  fn.call(obj)
  return obj
}
1
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
1
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
1
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
1
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
1
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
1
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
1
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
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20