js中关于函数的三种角色,以及this的五种情况

本文深入探讨JavaScript中函数的三种角色:普通函数、构造函数和对象方法,并解析this的五种不同情况。通过示例解释了如何在事件绑定、函数执行、new操作、箭头函数以及call/apply/bind方法中理解this的指向。此外,还提供了详细的代码示例来展示各种情况下this的行为,帮助读者掌握JavaScript中this的复杂用法。
摘要由CSDN通过智能技术生成

js中关于函数的三种角色,以及this的五种情况

函数的三种角色

/* 
 * 函数的三种角色
     + 普通函数:闭包作用域
     + 构造函数:可以通过 new 执行创建其实例
     + 普通对象:有键值对(也有原型链__proto__,指向Function.prototype这个匿名空函数)
  这三种角色之间没有必然联系

  所有的函数都是内置类Function的实例,所以一般函数的原型的constructor都是Function,即  fun.prototype.constructor: Function
  由于函数的原型是一个对象,所以 fun.prototype.__proto__: Object.prototype
*/

/*
 * Object 与 Function 的关系
    + 内置类Object是Function的一个实例;
    + Function也是Object的一个实例
    + Object还是Object的一个实例
    + Function也是Function的一个实例
  即:
   Object.__proto__.__proto__ === Object.prototype
   Function.prototype === Function.__proto__

 * 在 Function.prototype 上有call/apply/bind三个改变this指向的方法,所有函数都可以调用这三个方法
*/

 function Foo(){
  getName = function(){
    console.log(1);
  };
  return this;
}
Foo.getName = function(){
  console.log(2);
}
Foo.prototype.getName = function(){
  console.log(3);
}
var getName = function(){
  console.log(4);
}
function getName(){
  console.log(5)
}
Foo.getName();
// 把Foo看做对象,执行其私有方法
// => 2
getName();  
// 执行全局的方法,在代码执行到 var getName = ...时,其创建的新值覆盖变量提升时的全局getName
// => 4
Foo().getName(); 
// 执行Foo改变全局的getName的值后返回一个对象(winfow),再执行返回这个对象的getName
// window.getName()
// => 1
getName(); 
// 全局getName的值在Foo()的时候被重新赋值了
// => 1
new Foo.getName(); 
// 成员访问的优先级高于不带参数的 new
// 等价于 new (Foo.getName),把Foo.getName 执行,并返回一个实例对象
// => 2
new Foo().getName(); 
// 带参数列表的new(new xx()),跟成员访问的优先级一样,则从左往右进行计算
// 等价于 (new Foo).getName  new Foo执行返回Foo的一个实例,执行 实例.getName
// => 3
new new Foo().getName(); 
// 无法参数列表的new的优先级低于带参数列表的new和成员访问的优先级
// 等价于 new [(new Foo).getName]
// 即  new (Foo实例.getName)  把Foo实例.gerName 执行,并返回一个实例对象
// >= 3  

this的五种情况汇总

/* 
 * JS中 this 的五种情况汇总
    1、事件绑定:
        给当前元素的某个事件绑定方法,当事件行为触发,方法被执行,方法中的this一般都是当前操作的元素
        排除:IE6~8中,基于attachEvent进行的DOM2事件绑定,方法中的this是window
    2、函数执行(包括自执行函数)
       函数执行,看函数前面有没有点“.”,有点,则点前面是谁,函数中的this就是谁;没有点,则this是window
       + 在严格模式下,没有点,this是undefined
       + 匿名函数(自执行函数/回调函数)执行,一般this也是window(严格模式下是undefined),除非有特殊处理
    3、new 构造函数
       构造函数执行(new xxx),函数体中的this是当前类的实例
    4、箭头函数
       ES6中的箭头函数(或者基于{}形成的块级上下文),里面没有this,如果代码中遇到this也不是它自己的,而是它所在上下文中的this
    5、call/apply/bind
       基于Function.prototype上的call/apply/bind方法强制改变函数中的this执行
       + 对箭头函数没用,因为箭头函数中没有自己的this

*/

// 情况一:
 document.body.onclick = function(){
  console.log(this); // body元素
}
document.body.addEventListener('click', function(){
  console.log(this);// body元素
}) 

// 情况二:
// "use strict"
 function fn(){
  console.log(this);
}
let obj = {
  name: 'obj',
  fn: fn
}
//函数前面没有点
fn(); // window/undefined
// 函数前面有点
obj.fn(); // obj
// 自执行函数
(function(){
  console.log(this); // window/undefined
})();
// 回调函数
[1,2].forEach(function(a,b){
  console.log(this); // window/undefined
}); 

// 情况三:
 function Fn(){
  this.name = '构造函数';
  console.log(this);
}
Fn.prototype.sum = function(){}
let f = new Fn; // this -> f
Fn(); // this -> window
f.sum(); // this ->f
f.__proto__.sum(); // this -> f.__proto__ 

// 情况四:
 let obj = {
  name: 'obj',
  fn: function(){
    // obj.fn()执行: this ->obj
    console.log(this);
    setTimeout(function(){
      console.log(this); // window 因为这是一个回调函数
    },500);

    setTimeout(() => {
      // 这里的this用的是上级上下文中的this
      console.log(this); // obj
    }, 1000)
  }
}
obj.fn(); // fn中的this-> obj 

// 情况五:
let obj = {
  name: 'obj',
  fn:fn
};
function fn(){
  console.log(this);
}
fn(); // window
obj.fn();  // obj
fn.call(obj); // obj
console.log(fn.call(obj));  // undefined
obj.fn.apply(window); // window

/* 
 * call/apply/bind 的使用

  === call ===
   函数.call(context, parm1, parm2, ...)
   简单说明:把函数执行,让函数中的this指向context,并且把parm1、parm2...作为实参传递给函数
   详细说明:
     + 首先函数基于原型链__proto__找到Function.prototype.call方法,并且把call方法执行
     + call方法中的this就是当前操作的Function的实例---函数,传递给call方法的第一个实参是要函数中this的指向,剩余实参是依次要传递给函数的参数
     + call方法执行的过程中,实现了这样的处理:把 函数[call中的this] 执行,让 函数中的this指向context,并且把剩余实参传递给函数
   如果一个参数也不传,或者第一个参数传递的是null/undefined,给严格模式下,最后函数中的this都是window(严格模式下,不传是undefined,传了null/undefined,最后函数中的this也会改为对应的值)


  === apply ===
  函数.apply(context, [parm1, parm2,...])
  对比call和apply的区别只有一个,执行函数的时候,需要传递给函数的参数信息,在最开始传递给call/apply的时候,形式不一样
   + call 是需要把参数一个一个的传递给call
   + apply 是需要把参数放在一个数组中传递给apply


  === bind ===
    call/apply在执行的时候,都会立即把要操作的函数执行,并且改变它的this指向
    bind 是预先处理:指向bind只是预先把函数中需要改变的this等信息改变了并存储起来,此时函数并不会被立即执行,执行完bind会返回一个匿名函数,当后期执行匿名函数的时候,再去把之前需要执行的函数执行,并且改变this的指向

*/
let a = fn.bind(obj); // 执行bind的时候,fn是不会执行的
console.log(a); // 执行bind会返回一个函数,后期只有手动调用a()才会把里面的fn执行,并且按照执行bind时传递的this指向改变fn中的this值

// 需求:1s后执行fn,并且让fn中的this变为obj,传递10,20
setTimeout(fn.call(obj, 10, 20), 1000); // 这样写虽然this和参数都是想要的,但是并没有在1s后才执行,而是立即就执行了,这是因为在设置定时器的时候,已经基于call方法把fn给执行了
setTimeout(fn.bind(obj, 10, 20), 1000); // 把bind执行完后的结果(一个匿名函数)绑定给定时器,1s之后执行这个匿名函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值