zoukankan      html  css  js  c++  java
  • JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--前戏

    new关键字,call/apply/bind方法都和this的绑定有关,在学习之前,首先要理解this。

    一起来学习一下this吧

    首先。this是一个对象。

    对象很好理解,引用类型值,可以实现如this.xxx、this.xxx()等等的操作。

    验证:

    接着,this只有当执行上下文创建执行时,才会被绑定。

    在全局执行上下文中。(当浏览器碰到有JS代码要执行时,就会创建全局执行上下文,可以简单理解为打开网页就创建了)

    this指向window对象

    验证:

    1 <script>
    2   console.log(this === window);// true
    3 </script>

    函数内的this,只有当函数被调用时才会被绑定。

    首先,一般的函数内部都有this这个特殊的对象。(箭头函数除外)

    调用函数,引擎工作机制是创建一个函数执行上下文,进入函数执行上下文,执行这里的代码。(这里涉及到了执行栈、变量对象,作用域链等等)

    不看上面那段话也很好理解,代码演示:

    1 function fn() {
    2   console.log(this);
    3 }

    以上代码声明了一个函数fn,fn函数做的事情就是在控制台输出this。

    只有fn函数被调用时,我们才知道函数内部的this指向何处。

    那this究竟指向何处呢(箭头函数单独讨论,以下函数都是指function关键字声明的函数)

    第一种情况,当函数被普通调用时(如:fn()),this指向window对象。

    代码演示:

    1 fn(); // window

    这种情况下,不管函数嵌套的有多深。this指向的都是window。验证:

    1 function fn() {
    2   console.log(this);
    3 }
    4 const obj = {
    5   fn: function(fn) {
    6     fn();
    7   }
    8 }
    9 obj.fn(fn);// window

    第二种情况,当函数被new 关键字调用时(如:new Fn()),this指向new关键字新创建的对象。

    代码演示:

    1 function Fn() {
    2   console.log(this);
    3   window.myCheckThis = (obj) => {
    4     console.log(this === obj);
    5   }
    6 }
    7 const fn = new Fn();
    8 myCheckThis(fn);// true

    以上代码通过new关键字调用Fn函数后,返回一个对象。保存在fn中。

    在函数内部定义了一个全局的方法,检查函数内部的this指向。

    这里利用了箭头函数没有this这个特性,以及闭包的特性。(在函数内部创建一个函数,那么这个函数可以访问外层函数作用域链上的变量)

    通过作用域链访问到了外层函数的this。验证结果输出true。

    也可以只通过闭包实现这种验证。代码演示:

    1 function Fn() {
    2   const self = this;
    3   console.log(this);
    4   window.myCheckThis = function(obj) {
    5     console.log(self === obj);
    6   }
    7 }
    8 const fn = new Fn();
    9 myCheckThis(fn);// true

    以上代码,把外层函数的this保存在self中。内层函数通过作用域链就可以访问到这个这个变量。

    注意,内层函数直接访问的this。是内层函数自身的this。

    第三种情况,当函数被call--apply--bind方法调用时(如:fn.call(obj)),this指向这些方法绑定的对象。

    在实际中,函数的this会指向这些方法的第一个参数。

    当参数为null,undefined,window时,this指向window。

    当参数是基本类型值时,指向该基本类型值的包装类型值。代码演示:

    1 function fn() {
    2   console.log(this);
    3 }
    4 fn.call();// window,apply、bind同理
    5 fn.call('fn'); // String {"fn"},其他基本类型值、apply、bind同理

    以上都不是重点,emmmm.....

    当参数是引用类型值时,指向该对象。代码演示:

    function fn() {
      console.log(this);
    }
    fn.call({name: 'xm'});// {name: "xm"},apply、bind同理。

    需要注意的是:bind方法返回一个函数,apply方法第二个参数是数组或类数组对象。

    代码演示:

    function fn() {
      console.log(this);
    }
    const fn1 = fn.bind({name: 'xm'});
    fn1();// {name: "xm"}
    1 function add(a, b) {
    2   console.log(a + b);
    3 }
    4 add.apply(null, [1, 2]);// 3

    这三个方法还有很多别的用途,最常见的莫过于把类数组对象转换成数组对象了。

    代码演示:

    1 function fn(a, b, c, d) {
    2   const args = Array.prototype.slice.call(arguments, 0);
    3   return args.reduce((_a, _b) => {
    4     return _a + _b;
    5   }, 0);
    6 }
    7 console.log(fn(1, 2, 3, 4));// 10

    以上函数内部属性arguments对象是类数组对象,.slice()方法是在Array原型对象创建的方法,

    该方法内部this指向Array实例,通过call()方法,把this指向了arguments。

    第二行代码可以简单理解为arguments.slice(0),返回一个新的数组对象。

    然后在调用数组的reduce()方法。

    第四种情况,当函数作为对象的方法调用时,this指向该对象。

    这是很常见的一个情况,代码演示:

    1 const obj = {
    2   name: 'xm',
    3   say: function () {
    4     console.log(this.name);
    5   }
    6 }
    7 obj.say();// xm

    第五种情况,箭头函数的this。

    箭头函数本身没有this。在函数内部访问this。会沿着函数的作用域链往外查找this。

    代码演示:

    1 const obj = {
    2   say: () => {
    3     console.log(this);
    4   }
    5 }
    6 obj.say();// window

    以上在obj对象上创建了say方法。由于采用了箭头函数的书写方式,函数内部没有this。

    在内部访问this时,会沿着作用域链往外查找。往外就是全局执行上下文了,这里的this就是window。

    在看下面的例子:

    1 const obj = {
    2   say: function() {
    3     return () => {
    4       console.log(this);
    5     }
    6   }
    7 };
    8 const fn = obj.say();
    9 fn();// obj

    以上代码调用obj.say()方法,返回匿名的箭头函数,保存在fn中,接着调用fn()函数。

    由于箭头函数没有this,在内部访问this会沿着作用域链往外查找,它的外层函数是obj.say()函数。

    该函数在第八行被调用时,this绑定了obj对象。因此箭头函数的this也是指向obj。

    如果say方法也是箭头函数,会继续往外层找,那时就是指向window了。

    在看一个例子:

     1 const obj = {
     2   say: function() {
     3     return () => {
     4       console.log(this);
     5     }
     6   }
     7 };
     8 const say = obj.say;
     9 const fn = say();
    10 fn();// window

    以上代码,第九行普通调用say函数,this指向window。因此箭头函数的this也是指向window。

    那么箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?

    箭头函数是普通函数的另一种写法(简短的写法)。书写方式不同是第一大区别。

    箭头函数内部没有arguments对象,如果要用,可以用 rest 参数代替。即(...rest) =>{console.log(rest)}。

    箭头函数内部没有this对象。

    箭头函数不能通过new操作符调用。(因为箭头函数没有自己的this。也无法调用call/apply方法。也没有prototype属性。无法给实例添加指向构造函数原型对象的原型指针)

    箭头函数使用 yield 操作符。(生成器函数返回操作)

    看到这里,想必已经了如指掌,学有所成,胸有成竹... emmmm....

    接下来让我们实战一下吧,看看面试题是如何挖坑的。

     1 const length = 10;
     2 function fn() {
     3   console.log(this.length);
     4 }
     5 const obj = {
     6   length: 5,
     7   method: function(fn) {
     8     fn();
     9     arguments[0]();
    10   }
    11 };
    12 obj.method(fn, 1);

    点击查看答案

     这里主要考察函数普通调用时this的指向、作为对象的方法调用时this的指向,以及var、let、const声明变量的区别,加上对window对象、arguments对象的了解程度。

    首先,第一行代码在全局作用域中声明了length变量。值为10。

    在全局作用域下,var声明变量会成为window对象的属性,如有同名属性则覆盖。

    let、const声明的变量不在同一作用域(会产生自己的块级作用域,在作用域中直接通过标识符访问,不能用window.xxx访问),则没有这种功效了。

    如图:

    接着看第十二行,调用obj.method()方法。传入两个参数,第一个参数是函数fn。

    然后开始执行第八行,调用fn()函数,此时函数仅仅是普通调用,则函数内部的this指向window。

    接着开始执行第九行,注意,此时函数是作为arguments对象的方法调用,函数内部的this指向arguments对象。

    非常具有迷惑性,控制台输出下arguments对象一目了然。

  • 相关阅读:
    225. 用队列实现栈
    232. 用栈实现队列
    459.重复的子字符串(简单)
    28. 实现 strStr()(简单)
    剑指 Offer 58
    541. 反转字符串 II(简单)
    浏览器渲染页面的过程、重绘、重排以及页面优化
    隐藏一个元素的几种方法
    当我们在浏览器中输入一个URL后,发生了什么?
    Object.create() 和 new Object()、{} 的区别
  • 原文地址:https://www.cnblogs.com/caimuguodexiaohongmao/p/11176080.html
Copyright © 2011-2022 走看看