Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.手写call和apply #1

Open
gumingWu opened this issue May 15, 2022 · 6 comments
Open

1.手写call和apply #1

gumingWu opened this issue May 15, 2022 · 6 comments

Comments

@gumingWu
Copy link
Owner

先来一篇精彩的文章镇一下场子:mqyqingfeng/Blog#11

call的场景是什么呢?每次写一个api,肯定得先知道他的用途才可以。
一句话介绍calll:call() 方法在使用一个指定的 this 值和”若干个“指定的参数值的前提下调用某个函数或方法。

var foo = {
    value: '抱枕'
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 抱枕。 
bar(foo); // undefined

注意两点:

  • call 改变了 this 的指向,指向到 foo
  • bar 函数执行了

那我们来看看 这个call自己能怎么模拟出来吧~

思路:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数
@ClearMing
Copy link

ClearMing commented May 15, 2022

function mySymbol(obj) {
    //生成8位数的唯一值(防止默认值被占用)
    const unique = (Math.random() + new Date().getTime().toString(32).slice(0, 8))

    //防止生成重复
    if (obj.hasOwnProerty(unique)) {
        return mySymbol(obj)
    } else {
        return unique;
    }
}


Function.prototype.myCall = function (context) {
    //如果没有传值或传的值为空对象,则context指向window
    context ??= window
    const fn = mySymbol(context)
    context[fn] = this

    const arg = [...arguments].slice(1)//将类数组变为真的数组
    context[fn](...arg)//列表需要解构
    delete context[fn]
}

Function.prototype.myApply = function (context) {
    context ??= window
    const fn = mySymbol(context)
    context[fn] = this
    const arg = [...arguments].slice(1)
    context[fn](arg)//数组不需要解构
    delete context[fn]


}

@gumingWu
Copy link
Owner Author

手写call

本人比较懒,讲解都写在注释里

call,用于改变函数内部this,指向传入对象

第一版

Function.prototype.myCall1 = function (obj, ...args) {
  const fn = this;
  if (typeof fn !== "function") {
    throw new Error("输入一个函数啊!");
  }
  const key = Symbol(); // 一般题解是obj.fn = fn,但为了避免这个obj里真的有fn这个属性,用Symbol会比较安全
  obj[key] = fn;
  const res = obj[key](...args);
  delete obj[key];
  return res;
};

// 普通情况
function foo() {
  console.log(this);
}
const obj = {
  name: "hhh",
};
foo.call(obj); // { name: 'hhh' }

// 特殊情况,测试第一个兜底
obj.__proto__ = Function.prototype;
obj.myCall1(); // Error: 输入一个函数啊!

第二版

// 情况2,不输入参数,默认指向window,node指向globalThis
Function.prototype.myCall2 = function (obj, ...args) {
  const fn = this;
  if (typeof fn !== "function") {
    throw new Error("输入一个函数啊!");
  }

  // let defaultThis = null;
  // try {
  //   defaultThis = window; // 因为node环境没有window,所以执行这步的时候会报错
  // } catch {
  //   defaultThis = globalThis;
  // }
  // obj = obj || defaultThis;
  // 经提醒,window中globalThis就是window
  obj = obj || globalThis;

  const key = Symbol(); // 一般题解是obj.fn = fn,但为了避免这个obj里真的有fn这个属性,用Symbol会比较安全
  obj[key] = fn;
  const res = obj[key](...args);
  delete obj[key];
  return res;
};
foo.myCall2();

@Xieyusam
Copy link

Function.prototype.myCall = function (context, ...args) {
	let ctx = context || globalThis;
	const fn = this;
	if (typeof fn !== 'function') {
		throw new Error('this is not a function');
	}
	let key = Symbol();
	ctx[key] = fn;
	const res = ctx[key](...args);
	delete ctx[key];
	return res;
};
function foo() {
	console.log(this.name);
}
const obj = {
	name: 'xys'
};
foo.myCall(obj); 
var name = 'xys2';
foo.myCall();

@LuMMao
Copy link

LuMMao commented May 16, 2022

手写 call

测试案例:

// 测试 1
var foo1 = {
  value: 1,
};
function bar1() {
  console.log(this.value);
}
console.log('测试1');
bar1.myCall(foo1);

// 测试 2
var foo2 = {
  value: 1,
};
function bar2(name, age) {
  console.log(name);
  console.log(age);
  console.log(this.value);
}
console.log('测试2');
bar2.myCall(foo2, 'kevin', 18);

// 测试 3
var value3 = 1;
function bar3() {
  console.log(this.value3);
}
console.log('测试3');
bar3.myCall(null);

// 测试 4
var obj4 = {
  value: 1,
};
function bar4(name, age) {
  return {
    value: this.value,
    name: name,
    age: age,
  };
}
console.log('测试4');
console.log(bar4.myCall(obj4, 'kevin', 18));

// 测试 5
function foo5() {
  console.log(this);
}
var obj5 = {
  name: 'kevin'
}
obj5.__proto__ = Function.prototype;
obj5.myCall();

实现:

Function.prototype.myCall = function (context) {
  if(typeof this !== 'function') {
    throw new TypeError('this is not a function');
  }
  
  // 兼容 context 为 null 的情况
  // window 中 globalThis 指向 window,node 指向 globalThis
  context = context || globalThis;

  // 防止污染 context 内部属性
  const fn = Symbol("fn");
  context[fn] = this;

  // 获取参数,这里是 ES3 的实现方式,也可以用 ES6 的拓展运算符传参 context[fn](...[...arguments].slice(1))
  var args = [];
  for (var i = 1, len = arguments.length; i < len; ++i) {
    args.push("arguments[" + i + "]");
  }
  var result = eval("context[fn](" + args + ")");

  delete context[fn];

  return result;
};

@LuMMao
Copy link

LuMMao commented May 16, 2022

手写 apply

测试案例:

// 测试 1
var foo1 = {
  value: 1,
};
function bar1() {
  console.log(this.value);
}
console.log("测试1");
bar1.myApply(foo1);

// 测试 2
var foo2 = {
  value: 1,
};
function bar2(name, age) {
  console.log(name);
  console.log(age);
  console.log(this.value);
}
console.log("测试2");
bar2.myApply(foo2, ["kevin", 18]);

// 测试 3
var value3 = 1;
function bar3() {
  console.log(this.value3);
}
console.log("测试3");
bar3.myApply(null);

// 测试 4
var obj4 = {
  value: 1,
};
function bar4(name, age) {
  return {
    value: this.value,
    name: name,
    age: age,
  };
}
console.log("测试4");
console.log(bar4.myApply(obj4, ["kevin", 18]));

// 测试 5
function foo5() {
  console.log(this);
}
var obj5 = {
  name: 'kevin'
}
obj5.__proto__ = Function.prototype;
obj5.myApply();

实现:

Function.prototype.myApply = function (context, arr) {
  if (typeof this !== "function") {
    throw new TypeError('this is not a function');
  }

  // 兼容 context 为 null 的情况
  context = context || globalThis;

  // 防止污染 context 内部属性
  const fn = Symbol("fn");
  context[fn] = this;

  // 获取参数,这里是 ES3 的实现方式,也可以用 ES6 的拓展运算符传参 context[fn](...[...arguments].slice(1))
  var args = [];
  if (!arr || !arr.length) {
    var result = context[fn]();
  } else {
    for (var i = 0, len = arr.length; i < len; ++i) {
      args.push("arr[" + i + "]");
    }
    var result = eval("context[fn](" + args + ")");
  }

  delete context[fn];

  return result;
};

@fight-David
Copy link

fight-David commented May 16, 2022

手写 call

Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') throw Error('type error')

    context = context || window

    const fn = Symbol('fn')
    context[fn] = this
    let res = context[fn](...args)
    delete context[fn]
    return res
}
// 案例
var obj = {
	name: 'obj'
}

function sayName(...rest) {
      console.log(this.name, ...rest)
}
sayName.myCall(obj, 123, 321) // obj 123 321

手写 apply

Function.prototype.myApply = function (context, args) {
    if (typeof this !== 'function') throw Error('type error')

    context = context || window

    const fn = Symbol('fn')
    context[fn] = this
    let res = context[fn](args)
    delete context[fn]
    return res
}
// 案例
var obj = {
    name: 'obj'
}

function sayName(arr) {
    console.log(this.name, arr)
}
sayName.myApply(obj, [123, 321]) // obj [123,321]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants