hifizz / note-book

0 stars 0 forks source link

JS 的 call apply bind 和 new 以及原型(__proto__ & prototype) #1

Open hifizz opened 5 years ago

hifizz commented 5 years ago

以下代码都可在控制台运行看实际效果! 以下代码都可在控制台运行看实际效果! 以下代码都可在控制台运行看实际效果!

call 和 apply

call

假设有以下函数:

function foo(boo) {
    console.log(this.bar);
    console.log("hello");
    console.log(boo);
    return 42;
}

我们可以直接调用:

foo()

image

也可以用call()

foo.call()

image

看见结果了吗?你可以直接在foo后面加两个()foo()进行调用foo函数,也可以直接在调用foo的方法call foo.call() 进行调用foo函数,他们的结果完全一模一样!

我们再来看看call函数的函数签名:

/**
  * Calls a method of an object, substituting another object for the current object.
  * @param thisArg The object to be used as the current object.
  * @param argArray A list of arguments to be passed to the method.
  */
call(this: Function, thisArg: any, ...argArray: any[]): any;

现在我们调用call的时候传入想要的值

foo.call({bar: "hifizz"}, "moo")

image

OK,我们改变了 this的指向。call就是用来改变函数执行时this指向的方法。

apply

call 和 apply 不同的地方在于apply的第二个参数只能是数组。这一点我们可以从函数签名上看出来。

/**
  * Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function.
  * @param thisArg The object to be used as the this object.
  * @param argArray A set of arguments to be passed to the function.
  */
apply(this: Function, thisArg: any, argArray?: any): any;

实例测试一下:

foo.apply({bar: "stackfizz"}, ["fizz"])

image

bind

先看函数签名

/**
  * For a given function, creates a bound function that has the same body as the original function.
  * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
  * @param thisArg An object to which the this keyword can refer inside the new function.
  * @param argArray A list of arguments to be passed to the new function.
  */
bind(this: Function, thisArg: any, ...argArray: any[]): any;

现在我们来实现一把bind函数,用👆说过的call和apply

function bind(func, target) {
    return function (...argus) {
        return func.call(target, ...argus)
    }
}

const bindedFoo = bind(foo, {bar: "stackfizz"});
console.log(bindedFoo("fizz"));

image

当然,目前的实现我们还只能是传入要绑定的函数,我们可以使用上面学过的call和apply来修改一下:

function binding(target) {
    const func = this;
    return function (...argus) {
        return func.call(target, ...argus)
    }
}

const bindingFoo = binding.call(foo, {bar: "stackfizz"});
console.log(bindingFoo("fizz"));

image

当然,这样还是不够的,我们期望直接以这样的方式调用:

const bindedFooFunc = foo.binding({bar: "stackfizz"})

OK, 那么就来实现一下吧,很简单:

// 只需要这一句:
foo.__proto__.binding = binding;

const bindedFooFunc = foo.binding({bar: "stackfizz"})
console.log(bindedFooFunc("fizz"));

image

Bingo!

hifizz commented 5 years ago

new 运算符

假设现在有这么一个构造函数:

function BOOM(name, age) {
    this.name = name;
    this.age = age;
    return this;
}

BOOM.prototype.say = function() {
    console.log(`Name: ${this.name} ; age: ${this.age}`);
}

const boom = new BOOM("stackfizz", 25)
boom.say();

现在控制台执行以下我们能够看到:

image

要创建 BOOM 的新实例,必须使用 new 操作符。以new 运算符调用构造函数实际上会经历以下 4 个步骤:

基于上面的描述,我们可以自己构造一个函数来实现new的操作:

function newer(_class_, ...argus) {
    const newObj = {};
    newObj.__proto__ = _class_.prototype;
    return _class_.call(newObj, ...argus);
}

我们来调用它:

const boom1 = newer(BOOM, "stackfizz", 25)
boom1.say();

image

Bingo!我们实现了它:)

hifizz commented 5 years ago

prototype 和 proto

上面的例子有一个BOOM构造函数,我们使用了prototype来指定say方法

function BOOM(name, age) {
    this.name = name;
    this.age = age;
    return this;
}

BOOM.prototype.say = function() {
    console.log(`Name: ${this.name} ; age: ${this.age}`);
}

const boom = new BOOM("stackfizz", 25)
boom.say();

我们来看看boom的实例到底是什么东西?

image

哦,会发现 boom 是没有 prototype 属性的。没错!

其实呀,关于 __proto__ prototype 只要记住两点就OK了!

1. proto是每个对象都有的一个属性,而prototype是函数才会有的属性。

2. proto指向的是当前对象的原型对象,而prototype指向的,是以当前函数作为构造函数构造出来的对象的原型对象

e83bca5f1d1e6bf359d1f75727968c11_hd

hifizz commented 5 years ago

参考资料:

hifizz commented 5 years ago
function foo(boo) {
    console.log(this.bar);
    console.log("hello");
    console.log(boo);
    return 42;
}

console.log(foo.call());
console.log("------------");
console.log(foo.call({bar: "stackfizz"}, "fizz"));
console.log("------------");
console.log(foo.apply(this));
console.log("------------");
console.log(foo.apply({bar: "stackfizz"}, ["fizz"]));
console.log("------------");

function bind(func, target) {
    return function (...argus) {
        return func.call(target, ...argus)
    }
}

const bindedFoo = bind(foo, {bar: "stackfizz"});
const bindedFoo2 = bind(foo, {bar: "222222"});
const bindedFoo3 = bind(foo, {bar: "333333"});

console.log(bindedFoo("fizz"));
console.log("------------");

function binding(target) {
    const func = this;
    return function (...argus) {
        return func.call(target, ...argus)
    }
}
const bindingFoo = binding.call(foo, {bar: "stackfizz"});
console.log(bindingFoo("fizz"));

foo.__proto__.binding = binding;
const bindedFooFunc = foo.binding({bar: "stackfizz"})
console.log(bindedFooFunc("fizz"));
console.log("------bindingFoo------");

function newer(_class_, ...argus) {
    const newObj = {};
    newObj.__proto__ = _class_.prototype;
    return _class_.call(newObj, ...argus);
}

function BOOM(name, age) {
    this.name = name;
    this.age = age;
    return this;
}

// BOOM.prototype.say = () => {
//     console.log(`Name: ${this.name} ; age: ${this.age}`);
// }

BOOM.prototype.say = function() {
    console.log(`Name: ${this.name} ; age: ${this.age}`);
}

console.log(BOOM.prototype.constructor === BOOM);

const boom = new BOOM("stackfizz", 25)
boom.say();

const boom1 = newer(BOOM, "stackfizz", 25)
boom1.__proto__.sing = function() {
    console.log(`${this.name} sings song`);
}

boom1.say();
boom1.sing();
boom.sing();

boom.__proto__.high = function() {
    console.log("high");
}

boom1.high();
boom.high();

function PUMB() {}