一个前端知识点汇总

综合了学习过程中的知识点,比如this、闭包、BFC、ES6等,如果大佬们觉得还可以的话,求个star啦!

call和apply

  • 每个函数都包含两个非继承而来的方法:apply()和call()
  • 用途相同,都是在特定的作用域中调用函数
  • 接收参数方面不同,apply接收两个参数,一个是函数运行时的作用域(this),另一个是参数数组;call方法第一个参数与apply方法相同,但传递给函数的参数必须列举出来。

call()方法调用一个函数,其具有一个指定的this值和分别提供的参数:

fun.call(thisArg, arg1, arg2, ...)

apply()方法调用一个函数,其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数:

fun.apply(thisArg, [argsArray])

举个栗子???:

var one = {
    name: 'one',
    sayName: function(age) {
        console.log(`Hello, I'm ${this.name}, and I'm ${age} years old`)
    }
}
var day = {
    name: 'day'
}
one.sayName.call(day, 20) // Hello, I'm day, and I'm 20 years old
one.sayName.apply(day, [20]) // Hello, I'm day, and I'm 20 years old

fn.call(o)的原理就是先通过o.m = fn将fn作为o的某个临时属性m存储,然后执行m,执行完毕后将m属性删除。

大致就是这样:

day.fn = one.sayName
day.fn()
delete day.fn

所以可以模拟实现apply和call。

首先来看apply的模拟:

第一版

Function.prototype.applyOne = function() {
    var context = arguments[0]
    var args = arguments[1]
    context.fn = this
    eval(context.fn(args.join(','))) // args.join(',')返回的是string,所以需要进行一下特殊处理)
    delete context.fn
}

one.sayName.applyOne(day, [20]) // Hello, I'm day, and I'm 20 years old

第二版

要注意到的是,若this传入的是null,或者不传入,则会默认是全局环境,并且apply是有返回值的。

Function.prototype.applyTwo = function() {
    var context = arguments[0] || window
    var args = arguments[1]
    context.fn = this

    if (args == void 0) {
        return context.fn()
    } 
    var result = eval(context.fn(args.join(','))) // args.join(',')返回的是string,所以需要进行一下特殊处理
    delete context.fn
    return result
}

var name = "oneday"

var one = {
    name: 'one',
    sayName: function(age) {
        console.log(`Hello, I'm ${this.name}, and I'm ${age} years old`)
    }
}
var day = {
    name: 'day'
}
one.sayName.applyTwo(null, [20]) // Hello, I'm oneday, and I'm 20 years old

emmmm...有一个问题就是万一context里面本来就有fn属性怎么办呢...对于es6而言,可以将fn设置为一个独特的Symbol值,如下:

var fn1 = Symbol('aaa')
var fn2 = Symbol('aaa')
fn1 == fn2 // false

但是毕竟symbol是es6的特性啊,所以在es5中可以使用产生随机数的方法,例如:

var fn1 = 'o' + Math.random()
var fn2 = 'o' + Math.random()

接下来就是apply:

Function.prototype.callOne = function() {
    var context = [].shift.applyTwo(arguments)
    var args = [].slice.applyTwo(arguments) // 将剩下的参数作为数组传入啊
    return this.applyTwo(context, args)
}

emmmm...第一个参数就是arguments的第一个(一般是this,或者没有),后面的参数就作为数组形式传入。

bind方法

bind方法创建一个新的函数,当被调用时,this值是传递给bind的第一个参数,它的参数是bind其他的参数和其原本的参数,返回的是由指定的this值和初始化参数改造的原函数拷贝。

fun.bind(thisArg[, arg1[, arg2[, ...]]])

实例:

var name = '2333'
function Person(name) {
    this.name = name
    this.sayName = function() {
        setTimeout(function() {
            console.log(`Hello, I'm ${this.name}`)
        }, 1000)
    }
}

var oneday = new Person('1111')
oneday.sayName() // Hello, I'm 2333

但是下面这样就是1111~

this.sayName = function() {
    setTimeout(function() {
        console.log(`Hello, I'm ${this.name}`)
    }.bind(this), 1000)
}

var oneday = new Person('1111')
oneday.sayName() // Hello, I'm 1111

而且还有偏函数(Partial Functions),在mdn中是这么说的:

bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数作为bind()的第二个参数跟在this后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。

emmmm...对呀没看懂,于是就看例子啊...

function list() {
    return Array.prototype.slice.call(arguments)
}

var list1 = list(1, 2, 3) // [1, 2, 3]

// 所以listFun是拥有预设参数(5, 6)的,作为预设参数跟在第一个参数this后面
var listFun = list.bind(undefined, 5, 6)

// 后面传入的参数会跟在预设参数的后面
var list2 = listFun(7) // [5, 6, 7]
var list3 = listFun(8, 9) // [5, 6, 8, 9]

bind的模拟实现:

第一版

Function.prototype.bindOne = function() {
    var me = this // 缓存this
    var argArray = Array.prototype.slice.call(arguments, 1)
    return function() {
        return me.apply(arguments[0], argArray)
    }
}

但是上述的没有实现继续传参可以添加到原参数后的功能...所以有了第二版

第二版

Function.prototype.bindTwo = function() {
    var context = arguments[0]
    var me = this
    var argArray = Array.prototype.slice.call(arguments, 1)
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments)
        var finalArgs = argArray.concat(innerArgs)
        return me.apply(context, finalArgs)
    }
}

bind返回的函数如果作为构造函数,这个构造函数搭配new关键字出现的话,bind绑定的this需要被忽略,但是参数还要继续传入。意思就是bind的绑定比new的优先级要低。而且要在函数体内判断调用bind方法的一定要是一个函数。

复习一下new的作用:

  • 创建一个新对象
  • 新对象继承了该函数的原型(因此this就指向了这个新对象)
  • 为这个新对象添加属性和方法并返回这个新对象
var obj = {}
obj.__proto__ = Base.prototype
Base.call(obj)

第三版

Function.prototype.bindThree = function() {
    if (typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
    }

    // context指要把this绑定到的目标函数
    var context = arguments[0]

    // 这里的this指向调用bind的函数
    var me = this
    var argArray = Array.prototype.slice.call(arguments, 1)

    var F = function() {}
    F.prototype = this.prototype
    var bound = function() {
        var innerArgs = Array.prototype.slice.call(arguments)
        var finalArgs = argArray.concat(innerArgs)

        // 如果调用bind的函数是F的实例,那么this就还是指向调用bind的函数,如果不是F的实例,那么this就进行改变
        return me.apply(this instanceof F ? this : context, finalArgs)
    }
    bound.prototype = new F()
    return bound
}

参考:

不用call和apply方法模拟实现ES5的bind方法


oneday
279 声望4 粉丝