欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

学习JavaScript第十一天(函数的三种角色,this的五种情况)

程序员文章站 2022-04-09 17:16:22
学习JavaScript第十一天(函数的三种角色,this的五种情况)函数的三种角色/* * 函数的三种角色 + 普通函数:闭包作用域 + 构造函数:可以通过 new 执行创建其实例 + 普通对象:有键值对(也有原型链__proto__,指向Function.prototype这个匿名空函数) 这三种角色之间没有必然联系 所有的函数都是内置类Function的实例,所以一般函数的原型的constructor都是Function,即 fun.prototype...

学习JavaScript第十一天(函数的三种角色,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之后执行这个匿名函数

本文地址:https://blog.csdn.net/qq_32021429/article/details/109244346