Apply

86

Apply

  • Function.prototype.Apply

标题内容
Apply的定义什么是Apply函数?
Apply的使用如何使用Apply函数?
Apply的实现原理如何实现Apply函数?
Apply的示例Apply函数实现继承等

Apply的定义

  • apply()方法调用了一个具有给定this值的函数,以及以一个数组(或者类数组对象)的形式提供的参数。

注意: call()方法的作用和apply()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组

什么叫做具有给定this值的函数?

  • 可能大多数没有想过这个问题,但是今天在这里我们需要想一想了,并且以下代码也请在心情愉悦的状态去实操一下,因为作者本人也会存在书写错误的情况(失恋了PS:可能性很小或者被Bug困扰)。例如:
"use strict";
function getAge() {
  var y = new Date().getFullYear();
  return y;
}

var jackdan = {
  birth: 1995
}

getAge(); // 2021
getAge.apply(jackdan); // 2021
// 亲们,你们能看出来我写个apply操作究竟是要干嘛吗?
"use strict";

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
}

function getHeight() {
  return this.height;
}

var jackdan = {
  birth: 1995,
  height: 185
}


// getAge(); // Uncaught TypeError: Cannot read property 'birth' of undefined
getAge.apply(jackdan); // 26
getAge.apply(jackdan, []); // 26
getHeight.apply(jackdan); // 185
getHeight.apply(jackdan, []); // 185
// 使用push将元素追加到数组中。
// 由于push接受可变数量的参数,所以也可以一次追加多个元素。

// 但是,如果push的参数是数组, 它会将该数组作为单个元素添加, 
// 而不是将这个数组内的每个元素添加进去, 因此我们最终会得到一个数组内的数组。
// concat符合我们的需求,但它并不是将元素添加到现有数组,而是创建并返回一个新数组。
// 有没有办法可以在原数组中加入一个新的数组(参数是数组),而不用循环实现了,当然有了。
// 用apply实现

"use strict";

var arr = ['jackdan'];
var elements = [26, 185];

arr.push.apply(arr, elements); // [ "jackdan", 26, 185 ] 如果没有apply,就需要循环去实现
// 当然了,arr.push也是具有给定this值的函数,可以交叉验证一下

Apply的使用

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
}

getAge(); // NaN

var jackdan = {
  birth: 1995
}

getAge.apply(jackdan); // 26, this指向了jackdan
getAge.apply(jackdan, []); // 26, this指向了jackdan
"use strict";

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
}

var jackdan = {
  birth: 1995
}

// getAge(); // Cannot read property 'birth' of undefined

getAge.apply(jackdan); // 26, this指向了jackdan
getAge.apply(jackdan, []); // 26, this指向了jackdan
func.apply(thisArg, [argsArray])
/**
 * @params thisArg
 * 必须的参数。
 * 在func函数运行时使用的this值。请注意,this可能不是该方法看到的实际值:
 * 如果这个函数处于非严格模式下,则指定为null或undefined时会自动替换为指向全局对象
 * 原始值会被包装。
 *
 * @params argsArray
 * 可选的参数。
 * 一个数组或者类数组对象,其中的数组元素
*/

Apply的实现原理

Function.prototype.myApply = function(context, arr) {
  var ctx = Object(context) || window;
  ctx.fn = this;
  // context = Object(context) || window;
  // context.fn = this;

  var result;

  if(!arr) {
    result = ctx.fn();
    // result = context.fn();
  } else {
    var args = [];
    for(var i = 0, len = arr.length; i < len; i++) {
      args.push('arr[' + i +']');
    }

    result = eval('ctx.fn(' + args + ')'); // eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。
    // result = eval('context.fn(' + args + ')');
  }

  delete ctx.fn;
  // delete context.fn;
  return result;
}

var foo = {
  value: 1
}

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

bar.myApply(foo, [1, 2, 3]);
  • 不建议使用eval函数,为什么了?可能会有这样的疑惑。

  • eval()是一个危险的函数,它使用者调用者相同的权限执行代码。如果使用eval()运行的字符串代码被恶意方(不怀好意的人)修改(XSS-Cross Site Scripting),最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。更重要的是,第三方代码可以看到某一个eval()被调用时的作用域,这也有可能导致一些不同方式的攻击。相似的Function就不容易被攻击。

  • eval()通常比其他替代方法更慢,因为它必须调用JS解释器,而许多其他结构则可被现代JS引擎进行优化。

  • 此外,现代JavaScript解释器将javascript转换为机器代码。 这意味着任何变量命名的概念都会被删除。 因此,任意一个eval的使用都会强制浏览器进行冗长的变量名称查找,以确定变量在机器代码中的位置并设置其值。 另外,新内容将会通过eval()引进给变量, 比如更改该变量的类型,因此会强制浏览器重新执行所有已经生成的机器代码以进行补偿。 但是,(谢天谢地)存在一个非常好的eval替代方法:只需使用 window.Function。 这有个例子方便你了解如何将eval()的使用转变为Function()

// 糟糕的使用eval代码
function looseJsonParse(obj) {
  return eval("(" + obj + ")");
}
console.log(looseJsonParse(
  "{a:(4-1), b:function(){}, c:new Date()}"
))
// 不用eval的更好的代码
function looseJsonParse(obj) {
  return Function('"use strict"; return (' + obj + ')')();
}
console.log(looseJsonParse(
  "{a:(4-1), b:function(){}, c:new Date()}"
))
  • 至于分析,我会单独在eval()分析中进行详细阐述,这里就不作赘述了。

  • 我想到了一种ES6的优化方案。如下所示:

Function.prototype.myApply = function(context, arr) {
  let ctx = Object(context) || window;
  ctx.fn = this;

  let result;

  if(!arr.length) {
    result = ctx.fn();
  } else {
    result = ctx.fn(...arr);
  }

  delete ctx.fn;

  return result;
}

let arr = [26, 185];
let elements = ["jackdan"];

arr.push.myApply(arr, elements);

apply实现继承

  • ES6引入了类以及类继承的概念。
class Parent {
  constructor(age, height) {
    this.age = age;
    this.height = height;
  }

  toPrint() {
    console.log('age: ' + this.age + ', height: ' + this.height);
  }
}

class Child extends Parent {
  constructor(name, age, height) {
    super(age, height);
    this.name = name;
  }

  toPrint() {
    console.log('name: ' + this.name  + ', age: ' + this.age + ', height: ' + this.height);
  }
}

let child = new Child("jackdan", 26, 185);
child.toPrint();

console.log(child.__proto__ === Child.prototype);

console.log(Child.__proto__ === Parent);
console.log(Child.prototype.__proto__ === Parent.prototype);
function Parent(age, height) {
  this.age = age;
  this.height = height;
}

Parent.prototype.toPrint = function() {
  console.log('age: ' + this.age + ', height: ' + this.height);
}

function Child(name, age, height) {
  Parent.call(this, age, height);
  this.name = name;
}

let fTemp = function() {};

fTemp.prototype = Parent.prototype;

Child.prototype = new fTemp();
Child.prototype.toPrint = function() {
  console.log('name: ' + this.name  + ', age: ' + this.age + ', height: ' + this.height);
}

let child = new Child("jackdan", 26, 185);
child.toPrint();

developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/…