面向对象(oop)
- 标记语言:HTML5 / CSS3
- 编程思想
- 面向过程:C
- 面向对象: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都是这个实例
*/
构造函数执行时做了三件事情
- 创建出一个类的实例(对象)
- 把函数体中的this指向这个实例
- 如果没有返回值,返回当前的this(this指向实例的地址)
如果手动在构造函数里写了个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一次就创建一个新的实例对象返回
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)
- 内存图如下:
-
每个函数数据类型的值,都有一个天生自带的属性:prototype,这个属性的属性值是一个对象(“用来存储实例共用的属性和方法”)
- 普通的函数
- 类(自定义类和内置类)
-
在prototype这个对象中,有一个天生自带的属性: constructor,这个属性存储的是当前函数本身
Fn.prototype.constructor === Fn
-
每一个对象数据类型的值,也有一个天生自带的属性:__proto__或[[prototype]],这个属性指向“所属类的prototype”
- 普通对象、数组、正则、Math、日期、类数组等等
- 实例也是对象数据类型的值
- 函数的原型prototype属性的值也是对象数据类型的
- 函数也是对象数据类型的值
原型链查找机制
- 先找自己私有的属性,有则调用,没有继续找
- 基于__proto__找到所属类原型上的方法(Fn.prototype),如果没有则继续基于原型的__proto__往上找...一直找到Object.prototype为止
如上图(纯属个人理解,不对勿喷):可以看出
- 所有的类的__proto__最终都指向了Function类的prototype(因为__proto__指向所属类的原型),可以推断出,Function类是所有类的父类
- 因为原型是一个对象,所以所有的原型链最终都指向了Object的prototype
- 注:Object类的__proto__指向了Function类的prototype,而Function类的原型的__proto__又指向了Object类的prototype,这就成了一个闭环
从面向对象的角度来看内置类
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()
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)
//=> 编写两个方法 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'))
阿里经典面试题
-
函数数据类型:
- 普通函数
- 类(内置类 or 自定义类)
-
对象数据类型
- {}普通对象、[]数组对象、/^$/正则对象、日期对象、Math数学函数对象、arguments等类数组对象、HTMLCollection / NodeList元素或者节点集合类数组对象
- 实例也是对象数据类型
- 类的prototype也是对象数据类型
- 函数也是对象(函数有prototype属性,只有对象才会有属性名属性值,所以函数也是一个对象)
===============================================
函数有三种角色
-
普通函数
- 形参、实参、arguments、return、箭头函数
- 私有作用域(栈内存,执行上下文)
- 形参赋值 & 变量提升
- 作用域链
- 栈内存的释放和不释放(闭包)
- ...........
-
构造函数
- 类和实例
- prototype和__proto__原型和原型链
- instanceof (用来检测某个实例是否属于这个类 xxx instanceof xxx )
- constructor (prototype上的属性,指向当前方法)
- hasOwnProperty (检测某一个属性名是否为当前对象的私有属性 arr.hasOwnProperty('push'))
- ......
-
普通对象
- 它是由键值对组成的(有属性名和属性值)
- prototype属性(是一个对象)
- name属性(函数名)
- length属性 (形参的个数)
- __ proto__(所有的对象都有这个属性,它指向所属类的prototype,函数的这个属性指向Function类的prototype)
=========================================== Function函数内置类
-
每一个函数(普通函数、类)都是Function这个类的一个实例
- Function.__proto __ === Function.prototype (因为Function是一个对象,有__proto __属性,同时它又是所有类的基类,所有的函数都是它的实例,都指向它的prototype,所以Function.__proto __ === Function.prototype)
-
所有的对象都是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
函数执行的主体(不是上下文):意思是谁把函数执行的,那么执行主体就是谁
- 给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作元素本身
- 当方法执行的时候,如何确定执行主体(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跟在哪里执行在哪里创建的没有关系
- 构造函数执行时,this指向当前的实例对象
- 可以基于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
*/
- 箭头函数中,上面的4条作废。箭头函数中没有自己的this,它里面用到的this都是自己所处上下文中的this
- 箭头函数认为 this 和a,b,c这样的普通变量没有区别
- 就算用call给箭头函数传this,箭头函数也不支持this
- 箭头函数不支持用new调用
call方法
- 翻译:呼叫,呼唤
- 语法:函数.call([context], [params1], ...)
- 函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候完成了一些功能
- 让当前函数执行
- 把函数中的this指向改为第一个传递给call的实参
- 把传递给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
*/
注:规律
- 如果只有一个call,那么执行的就是左边的xxx.call()中的xxx()方法,方法中的this是传递的第一个实参
- 如果有两个及以上,执行的就是传入的第一个实参,方法中this是window或undefined
apply方法
- 翻译:申请,请求
- 语法:函数.call([context], [array])
- 用法:和call方法一样,都是把函数执行,并且改变里面的this关键字的,唯一的区别就是传递给函数参数的方式不同
- call是一个个传参
- apply是按照数组传参
- 其他的没有区别
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