对象 object

55 阅读15分钟

面向对象(oop)

  • 标记语言:HTML5 / CSS3
  • 编程思想
  1. 面向过程:C
  2. 面向对象:JAVA、PHP、C#(ASP.NET)、Javascript...
  • 大自然中所有的事物都可以称之为对象(因为所有东西都是我们要研究和面对的),“对象”本身是一个泛指
  • 把抽象的“对象”,按照特点进行详细的分类(大类,小类)把共同的东西进行抽取,放到对应的类别中 => “类”是对“对象”的一种细分,和公共部分的封装
  • 而类别中派生出来的具体事物叫做类的“实例”
  • 实例既有属于自己私有的东西,也有继承各个类别中的公有信息
  • 面向对象编程其实就是掌握“对象”、“类”、“实例”之间的关系和知识,例如:类的封装继承和多态等信息

单例设计模式(Singleton Pattern)

let name = "丫丫",
    age = 18,
    sex = "美女",
    name = "小帅",
    age = 81,
    sex = "帅哥"
    
 //=> 把描述当前事物特征的信息进行分组归类(减少全局变量的污染)
 //=> 这就是JS中的单例设计模式
 
 let beautiGirl = {
     name: "丫丫",
     age: 18
 }
 let oldMan = {
     name: '小帅',
     age: 81
 }
   
/*
    beautiGirl不仅仅被叫做变量(对象名),也被称为“命名空间”
    单例模式: 把描述事物的信息放到一个命名空间中进行归组,防止全局变量的污染
*/
  • 为了让单例模式变得高大上一些,真实项目中的单例模式都是这样处理的
let nameSpace = (function() {
    // 创建一些方法(闭包中的私有方法)
    let fn = function() {
        //...
    }
    //...
    return {
        name: 'xxx',
        fn: fn
    }
    window.fn = fn
})(window)
// 外面可以通过如下访问
nameSpace.name
nameSpace.fn()
fn()

工厂模式

批量化生产:把实现某个功能得代码进行封装,后期再想实现这个功能,我们直接执行函数即可

  • 低耦合:减少页面中冗余的代码
  • 高内聚:提高代码的重复使用率
function createPerson(name, age) {
    let persion = {}
    persion.name = name
    persion.age = age
    return persion
}

let beautyGirl = createPerson('啦啦'18)
let oldMan = createPerson('哈哈', 81)

构造原型模式(正统面向对象编程)

function CreatePerson(name, age) {
    this.name = name
    this.age = age
}

CreatePerson('张三'25)
//=> 这种方式是普通函数执行,调用时前面没有加.  所以this指向了window

let person = new CreatePerson
/*
    new这种执行方式叫做“构造函数执行模式”,此时的CreatePerson不仅仅是一个函数名,也被称为“类”,而返回的结果(赋值给person的)是一个对象,我们称之为“实例”,而函数体中出现的this都是这个实例
*/

无标题.png

构造函数执行时做了三件事情

- 创建出一个类的实例(对象)
- 把函数体中的this指向这个实例
- 如果没有返回值,返回当前的thisthis指向实例的地址)

如果手动在构造函数里写了个return,那么返回了什么

function CreatePerson(name, age) {
    this.name = name
    this.age = age
    // return 100; //=> 返回的还是类的实例
    // return {
    //    xxx: xxx
    // } //=> 返回的是{xxx: xxx}
    
    /*
        如果手动return 的是一个基本类型值,对返回实例没有影响,如果手动return的是一个引用类型的值,会把默认返回的实例给替换掉(所以在构造函数模式执行下,我们一般都不手动写return,防止把返回的实例给替换)
    */
    
}

构造函数执行,也具备普通函数执行的特点

  • 和实例有关系的操作一定是this.xxx,以为this是当前类创造出来的实例
  • 私有变量和实例没有必然的联系
function Fn(n) {
    let m = 10
    this.total = n + m
    this.say = function() {
        console.log(this.total)
    }
}
let f1 = new Fn(10)
let f2 = new Fn(20)
let f3 = new Fn
/*
=>new的时候不论加不加小括号,都相当于把Fn执行了,也创建了对应的实例,只不过不加小括号是不能传递实参的(当前案例中的形参n = undefined)
*/
console.log(f1.m) //=> undefined
console.log(f2.n) //=> undefined
console.log(f1.total) //=> 20
f2.say() //=> this=>f2 (谁.say()执行的,say函数里的this就指向谁) 
console.log(f1 === f2) //=> 每new一次就创建一个新的实例对象返回 

无标题1.png

instanceof

  • instanceof用来检测某个实例是否属于这个类
  • 局限性
    • 要求检测的实例必须是对象数据类型的,基本数据类型的实例是无法基于它检测出来的
  • typeof的局限性
    • typeof null的结果是"object"
    • typeof 的函数,数组,正则结果都是"object",无法区分
console.log(person1 instanceof CreatePerson)
//=> true, 检测person1是属于CreatePerson的实例

let ary = [12, 23]
console.log(ary instanceof Array) //=> true
console.log(ary instanceof RegExp) //=> false
console.log(ary instanceof Object) //=> true
  • 基本数据类型在JS中的特殊性
    • 一定是自己所属类的实例
    • 但是不一定是对象数据类型的
// 字面量创建方式(是Number类的实例,因为可以调用Number类里内置的公共方法)
let n = 10
console.log(n.toFixed(2))
console.log(typeof n) //=> "number", 所以它一定是基本类型

// 构造函数创建模式(创建出来的实例一定是对象类型的)
let m = new Number("10")
console.log(m) //=> Number => 10
console.log(typeof m) //=> "object"
console.log(m.toFixed(2))

// 区别:字面量方式创建出来的结果是基本类型,构造函数创建出来的结果是对象类型,其他没区别了
console.log(1 instanceof Number)  //=> false
console.log(new Number(1) instanceof Number) //=> true

原型和原型链基础模型

/*
    类: 函数数据类型
    实例: 对象数据类型
*/
function Fn() {
    /*
        new执行也会把类当成普通函数执行(当然也有类执行的一面)
        1、创建一个私有的栈内存
        2、形参复制 & 变量提升
        3、浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让函数中的this指向这个实例的地
        址 => “构造函数模式中,方法中的this是当前的实例”
        4、执行代码
        5、在我们不设置 return 的情况下,浏览器会把创建的实例对象默认返回
    */
    this.x = 100
    this.y = 200
    this.say = function() {
        console.log(this.x)
    }
 }
let f1 = new Fn()
let f2 = new Fn()

console.log(f1 === f2)
console.log(f1.x)
console.log(f2.y)
console.log(f1.say == f2.say)

  • 内存图如下:

无标题3.png

  • 每个函数数据类型的值,都有一个天生自带的属性:prototype,这个属性的属性值是一个对象(“用来存储实例共用的属性和方法”)

    • 普通的函数
    • 类(自定义类和内置类)
  • 在prototype这个对象中,有一个天生自带的属性: constructor,这个属性存储的是当前函数本身 Fn.prototype.constructor === Fn

  • 每一个对象数据类型的值,也有一个天生自带的属性:__proto__或[[prototype]],这个属性指向“所属类的prototype

    • 普通对象、数组、正则、Math、日期、类数组等等
    • 实例也是对象数据类型的值
    • 函数的原型prototype属性的值也是对象数据类型的
    • 函数也是对象数据类型的值

原型链查找机制

  • 先找自己私有的属性,有则调用,没有继续找
  • 基于__proto__找到所属类原型上的方法(Fn.prototype),如果没有则继续基于原型的__proto__往上找...一直找到Object.prototype为止

无标题4.png

如上图(纯属个人理解,不对勿喷):可以看出

  • 所有的类的__proto__最终都指向了Function类的prototype(因为__proto__指向所属类的原型),可以推断出,Function类是所有类的父类
  • 因为原型是一个对象,所以所有的原型链最终都指向了Object的prototype
  • 注:Object类的__proto__指向了Function类的prototype,而Function类的原型的__proto__又指向了Object类的prototype,这就成了一个闭环

从面向对象的角度来看内置类

无标题5.png

hasOwnProperty

  • 检查某一个属性名是否为当前对象的私有属性

    • own => 拥有
    • property => 属性
  • "in": 检查这个属性是否属于某个对象(不管是否私有属性还是公有属性,只要是它的属性,结果就为TRUE)

let ary = [10, 20, 30]
console.log('0' in ary) //=> TRUE, 0是索引,相当于属性
console.log('push' in ary) //=> TRUE
console.log(ary.hasOwnProperty('0')) //=> TRUE
console.log(ary.hasWonProperty('push')) //=> FALSE  push是它公有的属性,不是私有的

console.log(Array.prototype.hasOwnProperty('push'))
//=> TRUE 
console.log(Array.prototype.hasOwnProperty('hasOwnProperty'))
//=> FALSE 
console.log(Object.prototype.hasOwnProperty('hasOwnProperty'))
//=> TRUE 

/*
自己堆中有的就是私有属性,需要基于__proto__查找的就是公有属性(__proto__在IE浏览器中(Edge除外)保护起来了,不让我们在代码中操作它)
*/

自己写一个hasPubProperty方法

  • hasPubProperty: 检测某个属性是否为对象的公有属性
    • 是它的属性,但不是私有的
// 基于内置类原型扩展方法
Object.prototype.hasPubProperty = function(property) {
    //=> 验证传递的属性名合法性(一般只能是数字或者字符串的基本值)
    if (!["string", "number", "boolean"].includes(typeof property))
        return false;
    //=> 开始校验是否为公有属性(方法中的THIS就是要校验的对象)
    return (property in this) && !(this.hasOwnProperty(property))
}

console.log(Array.prototype.hasPubProperty('push')) //=> FALSE
console.log([].hasPubProperty('push')) //=> TRUE

原型链方法中的THIS问题

function Fn() {
    this.x = 100
    this.y = 200
    this.say = function() {
        console.log(this.x)
    }
}
Fn.prototype = {
    constructor: Fn,
    say: function() {
        console.log(this.y)
    },
    sum: function() {
        console.log(this.x + this.y)
    },
    write: function() {
        this.z = 1000
    }
}

let f1 = new Fn
f1.say()
//=> this: f1   => console.log(f1.x)  => 100
f1.sum()
//=> this: f1   => console.log(f1.x + f1.y)  => 300
f1.__proto__.say()
//=> this: f1.__proto__   => console.log(f1.__proto__.y)  => undefined
Fn.prototype.sum()
//=> this: Fn.prototype  =>  console.log(Fn.prototype.x + Fn.prototype.y)  => NaN
f1.write()
//=> this: f1   =>  f1.z = 1000   => 给f1设置一个私有的属性 z=1000

/*
    面向对象中关于私有/公有方法中的THIS问题
       1、方法执行,看前面是否有点,点前面是谁this就是谁
       2、把方法内的this都进行替换
       3、再基于原型链查找的方法确定结果即可
*/

面向对象练习题

function Fn() {
  this.x = 100
  this.y = 200
  this.getX = function() {
    console.log(this.x)
  }
}
Fn.prototype.getX = function() {
  console.log(this.x)
}
Fn.prototype.getY = function() {
  console.log(this.y)
}
let f1 = new Fn
let f2 = new Fn
console.log(f1.getX === f2.getX)
console.log(f1.getY === f2.getY)
console.log(f1.__proto__.getY === Fn.prototype.getY)
console.log(f1.__proto__.getX === f2.getX)
console.log(f1.getX === Fn.prototype.getX)
console.log(f1.constructor)
console.log(Fn.prototype.__proto__.constructor)
f1.getX()
f1.__proto__.getX()
f2.getY()
Fn.prototype.getY()

重构类的原型:让某个类的原型指向新的堆内存地址(重定向指向)

function Fn() {
  //=> ...
}
//=> 批量给原型设置属性方法的时候:
//=> 1. 设置别名
let proto = Fn.prototype
proto.getA = function() {}
proto.getB = function() {}
proto.getC = function() {}
proto.getD = function() {}

//=> 2. 重构类的原型
/*=> 注意: 
*  	* 重定向后的空间不一定有constructor属性(只有浏览器默认给prototype开辟的堆内存中
*		才存在constructor),这样导致类的原型机制不完整,所以需要我们手动再给新的原型空间设置
*		constructor属性。
*		* 在重新指向之前,我们需要确保原有原型的堆内存中没有设置属性和方法,因为重定向后,原有
*			的属性和方法就没啥用了(如果需要克隆到新的原型堆内存中,我们还需要额外的处理)
*			=> 但是内置类的原型,由于担心这样的改变会让内置的方法都消失,所以禁止了我们给内置类
*			原型的空间重定向,例如:Array.prototype = {...}这样没有用,如果相加方法
*			Array.prototype.xxx = function() {...}可以这样子处理
*/
Fn.prototype = {
  constructor: Fn,
  getA: function() {},
  getB: function() {}
}
function fun() {
  this.a = 0
  this.b = function() {
    alert(this.a)
  }
}
fun.prototype = {
  b: function() {
    this.a = 20
    alert(this.a)
  },
  c: function() {
    this.a = 30
    alert(this.a)
  }
}
var my_fun = new fun()
my_fun.b()
my_fun.c()

原型练习题02.png

function C1(name) {
  if (name) {
    this.name = name
  }
}
function C2(name) {
  this.name = name
}
function C3(name) {
  this.name = name || 'join'
}
C1.prototype.name = 'Tom'
C2.prototype.name = 'Tom'
C3.prototype.name = 'Tom'
alert((new C1().name) + (new C2().name) + (new C3().name))
//=> "Tomundefinedjoin"
function Fn(num) {
  this.x = this.y = num
}
Fn.prototype = {
  x: 20,
  sum:function() {
    console.log(this.x + this.y)
  }
}
let f = new Fn(10)
console.log(f.sum === Fn.prototype.sum)
f.sum()
Fn.prototype.sum()
console.log(f.constructor)
function Fn() {
  let a = 1
  this.a = a
}
Fn.prototype.say = function() {
  this.a = 2
}
Fn.prototype = new Fn;
let f1 = new Fn

Fn.prototype.b = function() {
  this.a = 3
}
console.log(f1.a)
console.log(f1.prototype)
console.log(f1.b)
console.log(f1.hasOwnProperty('b'))
console.log('b' in f1)
console.log(f1.constructor == Fn)

无标题6.png

//=>  编写两个方法 plus/minus 实现如下的执行效果
let n = 10
let m = n.plus(10).minus(5)
console.log(m)
//=> 10 + 10 -5 => 15

//=> n是Number的实例,要实现n.xxx必须要在所属类的prototype上加

~function() {
    const check_num = function(num) {
        let n = Number(num)
        return isNaN(n) ? 0 : n
    }
    const plus = function(x) {
        // this: 我们要操作的原始值数字 (this = xxx 我们不能给this手动赋值)
        x = check_num(x)
        return this + x
    }
    const minus = function(x) {
        x = check_num(x)
        return this - x
    }
    
    // 扩展到内置类的原型上
    Number.prototype.plus = plus
    Number.prototype.minus = minus
    
}()
let n = 10
let m = n.plus(10).minus(5)
console.log(m) //=> 15

重构数组的slice方法

~function() {
    const renew_slice = function(n, m) {
        n = parseInt(n)
        m = parseInt(m)
        //=> 当传入n时,起码有第一个参数
        if (!isNaN(n)) {
            if (isNaN(m)) {
                //如果没有传入第二个参数
                m = this.length
            }
            return this.filter((item, index) => {
                return n < 0 ? (index >= this.length + n) : (index >= n && index < m)
            })
        }
        return this
    }
    
    Array.prototype.renew_slice = renew_slice
}()

console.log(arr.renew_slice())
console.log(arr.renew_slice(2))
console.log(arr.renew_slice(2,4))
console.log(arr.renew_slice(-2))
console.log(arr.renew_slice(2.2))
console.log(arr.renew_slice('a'))

console.log(arr.slice())
console.log(arr.slice(2))
console.log(arr.slice(2,4))
console.log(arr.slice(-2))
console.log(arr.slice(2.2))
console.log(arr.slice('a'))

阿里经典面试题

  1. 函数数据类型:

    • 普通函数
    • 类(内置类 or 自定义类)
  2. 对象数据类型

    • {}普通对象、[]数组对象、/^$/正则对象、日期对象、Math数学函数对象、arguments等类数组对象、HTMLCollection / NodeList元素或者节点集合类数组对象
    • 实例也是对象数据类型
    • 类的prototype也是对象数据类型
    • 函数也是对象(函数有prototype属性,只有对象才会有属性名属性值,所以函数也是一个对象)

===============================================

函数有三种角色

  1. 普通函数

    • 形参、实参、arguments、return、箭头函数
    • 私有作用域(栈内存,执行上下文)
    • 形参赋值 & 变量提升
    • 作用域链
    • 栈内存的释放和不释放(闭包)
    • ...........
  2. 构造函数

    • 类和实例
    • prototype和__proto__原型和原型链
    • instanceof (用来检测某个实例是否属于这个类 xxx instanceof xxx )
    • constructor (prototype上的属性,指向当前方法)
    • hasOwnProperty (检测某一个属性名是否为当前对象的私有属性 arr.hasOwnProperty('push'))
    • ......
  3. 普通对象

    • 它是由键值对组成的(有属性名和属性值)
    • prototype属性(是一个对象)
    • name属性(函数名)
    • length属性 (形参的个数)
    • __ proto__(所有的对象都有这个属性,它指向所属类的prototype,函数的这个属性指向Function类的prototype)

=========================================== Function函数内置类

  1. 每一个函数(普通函数、类)都是Function这个类的一个实例

    • Function.__proto __ === Function.prototype (因为Function是一个对象,有__proto __属性,同时它又是所有类的基类,所有的函数都是它的实例,都指向它的prototype,所以Function.__proto __ === Function.prototype)
  2. 所有的对象都是Object这个类的实例

function Foo() {
  getName = function() {
    console.log(1)
  }
  return this
}
Foo.getName = function() {
  console.log(2)
}
Foo.prototype.getName = function() {
  console.log(3)
}
var getName = function() {
  console.log(4)
}
function getName() {
  console.log(5)
}
Foo.getName()
getName()
Foo().getName()
getName()
// 运算符的优先级,
//=> new Foo 这是无参数列表
//=> new Foo() 这是带参数列表
//=> (函数调用)成员访问(就是对象.属性)和new的带参数列表优先级高,从左往右执行
//=> 无参数列表的优先级比它们低,所以下面这个先执行顺序是
//=> 1. Foo.getName  => 指向了Foo的私有属性getName,它的地址暂时用AF1表示
//=> 2. new AF1()执行,相当于创建了AF1的实例,但其实可以把它看成普通函数执行,因为当前下this并没有用到
new Foo.getName()
new Foo().getName()

//=> 带参new先执行,然后成员访问,最后再new
//=> 相当于
//=> 1. let f = new Foo() //=> 返回Foo的实例对象
//=> 2. f.getName  // 指向了实例所属类的prototype,也就是Foo的prototype,暂时用AF2表示它的地址
//=> 3. new AF2()执行,相当于创建了AF2的实例,当成普通函数执行就行
new new Foo().getName()

THIS

函数执行的主体(不是上下文):意思是谁把函数执行的,那么执行主体就是谁

  1. 给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作元素本身
  2. 当方法执行的时候,如何确定执行主体(this)是谁?我们看方法前面是否有点(.),没有点(.)this就是window或者undefined(非严格模式下是window,严格模式下是undefined),有点(.),点前面是谁this就是谁。自执行函数中的this是window
var name = "丫丫"function fn() {
    console.log(this.name)
}
var obj = {
    name: "你好世界",
    fn: fn
}
obj.fn() //=> this是obj
fn() //=> this是window (非严格模式是window,严格模式是undefined)window.fn()把window. 省略了

(function() {
	// 自执行函数中的this是window或undefined
})()

let obj = {
	fn: (function(n){
		//把自执行函数执行的返回结果赋值给fn
		//this是window或undefined
		return function() {
			//=>fn等于这个返回的小函数
			//this是obj
		}
	})(10)
}

function fn() {
	//this: window或undefined
	console.log(this)
}
document.body.onclick = function() {
	// this: document.body
	fn()
}
//=> this跟在哪里执行在哪里创建的没有关系
  1. 构造函数执行时,this指向当前的实例对象
  2. 可以基于call / apply / bind 来强制改变一个方法中的this指向
//=> call / apply / bind 是Function的原型上提供的三个方法,每一个函数都可以调用这些方法
//=> 这些方法都是用来改变函数中的THIS指向的

function fn() {}
fn.call()
/*
    fn函数基于原型链找到Function.prototype上的call方法,并让其执行(执行的是call方法:方法中的this是fn)
*/
fn.call.call()
/*
    先执行call方法,调用者是fn.call,方法中的this是fn.call
*/
  1. 箭头函数中,上面的4条作废。箭头函数中没有自己的this,它里面用到的this都是自己所处上下文中的this
    • 箭头函数认为 this 和a,b,c这样的普通变量没有区别
    • 就算用call给箭头函数传this,箭头函数也不支持this
    • 箭头函数不支持用new调用

call方法

  • 翻译:呼叫,呼唤
  • 语法:函数.call([context], [params1], ...)
  • 函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候完成了一些功能
  1. 让当前函数执行
  2. 把函数中的this指向改为第一个传递给call的实参
  3. 把传递给call其余的实参传递给当前函数当做形参
  • 如果执行call一个实参都没传递,或者传null(undefined),函数中的this在非严格模式下指向window,严格模式下指向undefined
window.name = 'WINDOW'
let obj = { name: 'OBJ' }
let fn = function(n, m) {
  console.log(this.name)
}
fn(n, m); //=> this: window  //=> 输出'WINDOW'	n=10, m=20

fn.call(obj) //=> this: obj  //=> 输出'OBJ'		n,m = undefined
fn.call(obj, 10, 20) //=> this: obj  //=> 输出'OBJ'		n=10, m=20

fn.call() //=> this: window  //=> 输出'WINDOW'  n,m = undefined
fn.call(10, 20) //=> this: 10  //=>   n = 20, m = undefined

fn.call(null) //=> this: window  //严格模式下this: undefined, 非严格模式下this: window
fn.call(null, 10, 20) //=> this: window		n=10, m=20

Object.prototype.toString()
// this: Object.prototype
Object.prototype.toString.call(100)
// this: 100

===========================================================================
/*
*		不用call,让fn执行的时候,方法中的this指向obj
*/
  obj.fn()  //=> 这样写this就指向了obj,但是会报错  //=> obj.fn is not a function
//=>  因为此时obj中并没有fn这个属性
----------------解决办法----------------------
obj.fn = fn;
obj.fn();		//=> this: obj		//=> 输出'OBJ'
delete obj.fn

基于原生js实现内置call的方法

  • 浏览器原生是拿C++写的,和原生call方法还是有很大区别
~function() {
    const myCall = function(context) {
        //=> context是传进来的让this指向的那个对象
        //=> 现在的this是当前执行myCall方法的那个函数
        //=> 相当于把这个函数赋值给context的一个属性,然后调用,函数中的this的指向就变成context了
        //=> 万一没传或者传了null或undefined, this就指向window
        
        context = context || window
        context.$fn = this
        let result = context.$fn(...[...arguments].slice(1))
        delete context.$fn
        return result
    }
    
    Function.prototype.myCall = myCall
}()

let obj = {
    x: 100,
    y: 200
}
var x = 50,
    y = 30
const fn = function(n, m) {
    console.log('形参', n, m)
    console.log(this.x + this.y)
}
fn() //=>this: window  输出50  形参: undefined  undefined
fn.call(obj, 1, 2) //=> this: obj  输出300   形参: 1  2
fn.myCall(obj, 3, 4) //=> this: obj  输出300  形参:3  4

阿里经典面试试题

function fn1() { console.log(1) }
function fn2() { console.log(2) }

//=> call方法执行可以看成当前指向的this的函数执行的同时,把执行函数里面的this指向call的第一个参数

fn1.call(fn2)
/* 当前执行call()方法,方法里的this指向fn1,所以this()相当于fn1()执行,同时把执行函数里的this指向了 fn2
*/
//=> 输出2

fn1.call.call(fn2)
/*
    1、先执行call(fn2)方法,相当于this()执行 也就是fn1.call()执行,同时执行函数里面的this指向了fn2
    2. 执行fn1.call(),相当于执行this(),上一步this指向了fn2,所以fn2()执行,没有形参,此时执行函数
    里面的this指向了window
*/

Function.prototype.call(fn1)
/*
    首先执行call(fn1)方法,相当于this()执行,也就是Function.prototype()执行,因为它是一个
    匿名函数,执行没有输出值,也没有返回结果,此时执行函数里面的this指向fn1
*/

Function.prototype.call.call(fn1)
/*
    1. 首先执行call(fn1)方法,相当于this()执行,也就是Function.prototype.call()执行,此时
      执行函数里面的this指向了fn1
    2. 执行Function.prototype.call(),因为上一步this指向了fn1,所以this()相当于fn1(),因为
      没有形参,所以fn1()里面的this指向window
*/

注:规律

  1. 如果只有一个call,那么执行的就是左边的xxx.call()中的xxx()方法,方法中的this是传递的第一个实参
  2. 如果有两个及以上,执行的就是传入的第一个实参,方法中this是window或undefined

7a85af93621aad35f774ebdcd8db38b.png

apply方法

  • 翻译:申请,请求
  • 语法:函数.call([context], [array])
  • 用法:和call方法一样,都是把函数执行,并且改变里面的this关键字的,唯一的区别就是传递给函数参数的方式不同
  1. call是一个个传参
  2. apply是按照数组传参
  3. 其他的没有区别
let obj = {name: 'OBJ'}
let fn = function(n,m) {
  console.log(this.name)
}
//=> 让fn方法执行,让方法中的this变为obj,并且传递10, 20
fn.call(obj, 10, 20)
fn.apply(obj, [10, 20])

======================================================
// 重写apply
~function() {
    const myApply = function(context, array) {
        context = context || window
        context.$fn = this
        let result = context.$fn([...array])
        delete context.$fn
        return result
    }
    
    Function.prototype.myApply = myApply
}()

bind方法

  • 翻译:绑定,捆绑
  • 用法:和call / apply一样,也是用来改变函数中的this关键字的,只不过基于bind改变this,当前方法并没有被执行,类似于预先改变了this指向而已
let obj = {name: 'OBJ'}
function fn() {
  console.log(this.name)
}
document.body.onclick = fn;  //=> 当事件触发,fn中的this: body
//=> 点击body,让fn中的this指向obj
//=> document.body.onclick = fn.call(obj)
//=> 基于call / apply 这样处理,不是把fn绑定给事件,而是把fn执行后return的结果绑定给事件
document.body.onclick = function() {
  // this: body
  fn.call(obj)
}
//=> 这样也能实现,每次点击的时候执行一次fn函数并改变指向
document.body.onclick = fn.bind(obj)
/*
=> bind的好处是:通过bind方法只是预先把fn中的this修改为obj,此时fn并没有执行,当点击事件触发才会
执行fn(call / apply都是改变this的同时立即把方法执行)
=> 在IE6 ~ 8中不支持bind方法
=> 这种预先做了某些事情的思想被称为"柯理化函数"

获取数组中的最大值和最小值

let ary = [12, 24, 13, 8, 35, 15]
// 解决方案一:先排序,第一项和最后一项就是我们需要的
ary.sort((a, b) => a - b)
let min = ary[0]
let max = ary[ary.length - 1]
console.log(min, max)

// 解决方案二:Math.max / Math.min
/*=> Math.max/min要求我们传递的数据时一项项传递进来,获取一堆数中的最大最小,而不是获取一个
*		数组中的最大最小
*/
// 1. 基于ES6的展开运算符
let min = Math.min(...ary)
let max = Math.max(...ary)
console.log(min, max)
// 2. 利用apply来实现即可(this无所谓,主要是利用apply给给函数传参,需要写成一个数组的特征)
let min = Math.min.apply(null, ary)

/*	解决方案三:假设法(假设第一个是最大的,让数组中的每一项分别和当前假设的值比较,如果比假设的
*		值大,则把最大的值设为新的假设值,,继续向后比较即可)
*/
let max = ary[0]
for (let i = 1; i < ary.length; i++) {
  	let item = ary[i]
    item > max ? max = item : null
}
console.log(max)

箭头函数及this问题

ES6中新增了创建函数的方式: “箭头函数”

  • 箭头函数简化了创建函数的代码
/*=> 箭头函数的创建都是函数表达式的方式(变量=函数),这种模式下,不存在变量提升,也就是函数
*		只能在创建完成后被执行(也就是创建的代码之后执行)
*/
const fn = ([形参]) => {
  //函数体 (return)
}
fn([实参])
// => 形参只有一个,小括号可以不加
// => 函数体中只有一句话,并且是return xxx的,可以省略大括号,如果return的是一个对象,要加小括号
const fn = b => b+1
const fn = b => ({'name': b})

function fn(n) {
  return function(m) {
    return m+(++n)
  }
}
let f = fn

const fn = n => m => m+(++n)
  • 箭头函数中没有arguments,但是可以基于剩余运算符获取实参集合,而且ES6中是支持给形参设置默认值的
let obj = {}
let fn = (context = window, ...args) => {
// ...args:就是剩余运算符(把除前面已定义的形参外,其它传递的实参信息都存储到args这个数组集合中)
  console.log(arguments) // argunments is not defined
// 箭头函数中没有arguments
  console.log(args)
}
fn(obj, 10, 20, 30) 
//=> context: obj  args: [10, 20, 30]
fn()
//=> context: window  args: []
  • 箭头函数中没有自己的THIS,它里面用到的THIS都是自己所处上下文中的THIS(真实项目中,一旦涉及THIS问题,箭头函数慎用)
window.name = "WINDOW"
let obj = { name: 'OBJ' }
let fn = n => { console.log(this.name) }
fn(10)	//=> this: WINDOW
fn.call(obj, 10)  //=> this: WINDOW 不是预期的OBJ,所以call并不能改变箭头函数的指向
document.body.onclick = fn 
/*
	正常情况: this应该指向执行事件的元素本身 => body
  结果: this: WINDOW
*/
let obj1 = {
  name: '小米',
  fn: () => console.log(this.name)
}
//=> this: WINDOW
let obj2 = {
  name: '小米',
  fn: function() { console.log(this.name) }
}
// this: obj2  输出小米

let obj3 = {
  name: '小米',
  fn: function() {
    // this: obj3  普通函数是有自己的this的
    let f = () => {
      console.log(this)
    }
  
    return f;
  }
}
let f = obj3.fn()
f() //=> this: obj3