Function类、call、apply、bind

146 阅读6分钟

一、Function类:函数类

所有的函数(类、普通函数)都是Function的一个实例

内置类:


Array Number String Boolean Object Date Function 等

console.log(typeof Array);  function
console.log(typeof Number);  function

内置类和Function类

内置类都是Function类实例,而实例都有一个__proto__ 指向所属类的原型对象

js中所有的函数都是Function的实例,那么内置类,如Array是函数,所以Array也是Function的实例;

console.log(Array instanceof Function);

既然是实例,那么一定也会有原型关系

console.dir(Array);  通过打印发现Array也是一个对象,它也有__proto__,根据原型关系,Array的__proto__应该指向的是Function的prototype

Array.__proto__ === Function.prototype;  true

console.log(Date instanceof Function);  true
console.log(Date.__proto__ === Function.prototype);  true
console.log(RegExp instanceof Function);  true
console.log(RegExp.__proto__ === Function.prototype);  true
console.log(Object instanceof Function);  true
console.log(Object.__proto__ === Function.prototype);  true

console.log(Function.prototype.__proto__ === Object.prototype);  true

因为Object也是一个类,所以也是一个函数,所以也是Function的实例.所以Object.proto指向Function.prototype 而Function本身也是一个类,也是一个函数,所以Function也有prototype,而prototype也是一个对象,所以Function.prototype.proto 又指向了 Object.prototype

Function 和 Object的关系

Object.__proto__ 指向Function.prototype


console.log(Object.__proto__ === Function.prototype);


Function.prototype.__proto__ 指向Object.prototype


console.log(Function.prototype.__proto__ === Object.prototype);

所有的函数都是Function的实例

console.log(Array instanceof Function);

所有的引用数类型(普通对象、实例对象、函数、类、数组、Date)的都是Object这个基类的实例,所以函数也是对象;

console.log(Function instanceof Object);
console.log(Array instanceof Object);
console.log(Date instanceof Object);
let obj = {
  id: 1
};
console.log(obj instanceof Object);

function fn() {
  console.log('fn')
}
fn.name = '你好';
fn.age = 10;
console.log(fn.age);
console.log(fn.age);

总结

所有的函数数据类型都是 Function的实例

function Fn() {}

console.log(Fn instanceof Function);  true

Function 函数类 本身也是一个函数

console.log(typeof Function);  function
console.log(Function instanceof Function);  true 所以Function也是自己的的一个实例

因为Function是自己的实例,所以Function.proto 指向自己的prototype

console.log(Function.__proto__ === Function.prototype);

Function也是Object基类的实例:所以函数也是对象,可以有自己的私有属性

console.log(Function instanceof Object);  true

js的内置引用类型都是Function的实例

console.log(Object instanceof Function);
console.log(Object.__proto__ === Function.prototype)

二、函数的三种角色

函数的三种角色:

作为一个普通函数执行(形参、实参、返回值) 作为一个类(new Fn 构造函数执行) 函数也是一个普通对象(通过 .属性名 或者 ['属性名'] 获取私有属性);

1. 普通函数

function sum(a, b) {
  var x = 1;
  var y = 12;
  var z = 123;
  return a + b + x + y + z;
}
var result = sum(1, 3);
console.log(result);

普通函数的执行过程:

开辟一个新的作用域 形参赋值 变量提升 函数体从上到下执行 销毁作用域

2. 构造函数(类):

2.1 每个构造函数都有一个prototype属性,它的值是一个对象,用来存放当前类型的公有的属性和方法 2.2 必须通过new操作符调用函数才能返回一个实例对象;

function Teacher(n, a, s, f) {
  // 通过this.xxx = xxx给实例对象添加私有属性
  this.name = n;
  this.age = a;
  this.subject = s;
  this.from = f;
}

在原型上增加的方法都是这个类型公有的属性和方法

Teacher.prototype.teach = function () {
  console.log(`${this.name} 你好 ${this.subject} 你好啊`)
};
let t = new Teacher('你好', 18, 'JS', '你好啊');

new 执行构造函数:

新开辟一个作用域 形参赋值 变量提升 隐式创建一个当前类的实例对象,并且把构造函数中的this指向当前实例 执行构造函数中的代码 隐式返回实例对象,相当于return this 销毁作用域

console.log(t.name);
console.log(t.age);
t.teach();  // 调用Teacher的公有方法

3. 作为一个普通对象(所有引用数据类型的都是Object的一个实例);

function fe(a, b) {
  console.log('I am an excellent FE cultivated by ZhuFeng');
}
fe(1, 2);

console.dir(fe);  {name: 'fe', length: 形参个数...}

console.log(fe.length);  2 形参个数
console.log(fe.name);  函数名

把函数当做普通对象使用,就像操作普通对象一样操作对象;通过这样的方式添加给函数(普通函数、构造函数)的属性或者方法称为静态属性或方法。

fe.age = 10;
fe.title = 'hello';
fe.greeting = function () {
  console.log('hello world')
};
console.log(fe.age);
console.log(fe.title);

let f1 = new fe();
console.log(f1.age);  undefined
console.log(f1.title);  undefined
console.log(f1);
console.log(fe.prototype);
console.dir(fe);

注意:通过 函数名.xxx = xxx 添加的属性都是这个函数的私有属性。如果这个函数被当做构造函数使用(用new调用)时,这些属性既不是实例的属性也不是实例的公有属性,只能通过 函数名.xxx 的方式获取;

Array.isArray()

是数组Array的静态方法,只能通过Array自己调用;

Array.isArray() 检测一个值是否是一个数组,如果是返回true,不是就返回false

console.log(Array.isArray([]));  true
console.log(Array.isArray(1));  false

三、call、apply、bind

this

是js代码执行时的环境对象,一般在函数中使用,在函数执行时,根据函数的调用方式不同而不同,在运行时不能通过赋值的方式修改;

1. this的常见情况

事件中的this是绑定当前事件的元素; 自执行函数中的this指向window; 定时器回调函数中的this指向window; 全局作用域的this指向window 方法调用时看方法执行前没有有点,如果有点前面是谁this就是谁,没有就是window 箭头函数中的this是箭头函数声明时所在作用域中的this 构造函数中的this指向当前实例

Function.prototype 上的三个方法call、apply、bind 供Function的实例用来修改函数中的this指向


console.log(Function.prototype);

1. call Function.prototype.call

function fe(a, b) {
  console.log(a, b);
  console.log(this);
}

fe(1, 2); // this -> window

使用call方法修改this 语法:函数名.call(ctx, 实参1, 实参2.....) 参数:ctx 将函数中的this修改为ctx; 从第二个参数开始,后面的参数都是传递给函数执行的实参 作用: 修改方法中的关键字为call方法的第一个实参ctx,并且把后面的参数当做实参传给函数,最后让函数执行;

var obj = {
  id: '0511120117'
};
fe.call(obj, 2, 5);

特殊情况:

fe.call(null, 1, 2);
fe.call(undefined, 1, 2);
fe.call();
// call的第一个参数传递 null、undefined、或者不传时函数的this是window

2. apply 修改函数中的this关键字

语法:函数名.apply(ctx, [实参1, 实参2....]) 参数:ctx 将函数中的this修改为ctx;第二个参数是一个数组,数组项都是传递给函数的实参; 作用:修改函数中的this关键字,并且把接收一个由实参组成的数组,最后把这个数组项作为实参传给函数,并且让函数执行

fe.apply(obj, [12, 13]); // 虽然这里传递了一个数组给函数,但是函数接收到的仍然是一个一个的实参

call 和apply的区别:二者都是用来修改函数中的this关键字的;但是二者最后给函数传递实参的方式不同,call是一个一个的传递,apply是把实参放到一个数组中打包传递给函数。

bind 修改函数中的this关键字(绑定this关键字):

Function.prototype.bind

语法:函数.bind(ctx, 实参1, 实参2....) 作用:绑定函数中的this关键字,并且返回一个绑定了this的新函数;注意:bind不会让函数执行

function f(a, b, c) {
  console.log(a, b, c);
  console.log('ff', this);
  return a + b + c;
}

let obj2 = {
  college: 'x'
};

let f2 = f.bind(obj2);
console.log(f2 === f); // false
f2(1, 2, 3);
f2(1, 3, 5);
console.log(f2);

bind还有一个作用:绑定函数的参数

let f3 = f.bind(obj2, 10, 20);
f3(13); // 10 20 13
f3(14); // 10 20 14

let f4 = f.bind(obj2, 12); // x = 12
let f5= f4.bind(obj2, 13); // x = 12 y = 13
let f6 = f5.bind(obj2, 14); // x = 12 y = 13 z = 14
f6(); // 12 13 14

bind方法实现函数柯里化

function sum(a, b, c) {
  return a + b + c;
}

function curingSum(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    }
  }
}
curingSum(1)(2)(3);

let c1 = sum.bind(null, 1);
let c2 = c1.bind(null, 2);
let c3 = c2.bind(null, 3);
let r = c3();
console.log(r);