天空闲话 发表于 前天 09:50

JavaScript中的this

1.this的几种情况

1.1全局情况中的 this

在全局情况中(不在任何函数或对象内部),this指向全局对象(在欣赏器情况中是window,在node.js中是global)。
console.log(this === window); // 在浏览器控制台中输出 true
1.2 默认绑定

当一个函数被直接调用(以函数情势调用,方法前面无小数点)时,this在非严酷模式下指向全局对象(window),在严酷模式下指向undefined。
function regularFunction() {
console.log(this);
}
regularFunction(); // 非严格模式下指向 window,严格模式下为 undefined

// 演示严格模式下的情况
(function () {
"use strict";
regularFunction();
})();
1.3 隐式绑定

当函数作为对象的方法被调用时,this指向调用该方法的对象。
var person = {
name: "John",
sayName: function () {
    console.log(this.name);
}
};

person.sayName(); // 输出 "John",这里的 this 指向 person 对象
1.4 new绑定

以构造函数情势调用,this实行新创建的对象实例。
function Person(name) {
this.name = name;
this.sayHello = function () {
    console.log("Hello, I'm " + this.name);
};
}

var john = new Person("John");
john.sayHello(); // 输出 "Hello, I'm John",这里 this 指向 john 实例
构造函数怎么实行创建对象的过程详见我写的文章https://blog.csdn.net/fageaaa/article/details/141924289。
1.5 箭头函数中的this

箭头函数没有自己的this,它的this继续自外层作用域的this,与调用方式无关。
1.6 事故处置惩罚函数中的this

在DOM事故处置惩罚函数中,this通常指向触发事故的元素。
<button id="myButton">Click me</button>

<script>
var button = document.getElementById("myButton");
button.onclick = function () {
    console.log(this); // 点击按钮时,这里的 this 指向按钮元素
    //打印 :<button id="myButton">Click me</button>
};
</script>
1.7 显式绑定

使用apply、call、bind调用时候,this表示指定的谁人对象。将在下面解说。
1.8 代码示例

// 普通函数
function outerFunction() {
this.name = "Outer";
var innerFunction = function () {
    console.log(this.name);
};
innerFunction();
}

// 箭头函数
function outerFunctionWithArrow() {
this.name = "Outer with Arrow";
var innerFunction = () => {
    console.log(this.name);
};
innerFunction();
}

new outerFunction(); // 输出 undefined,因为 innerFunction 中的 this 指向全局对象,全局对象没有 name 属性
new outerFunctionWithArrow(); // 输出 "Outer with Arrow",箭头函数的 this 继承自outerFunctionWithArrow 的 this
2.call、apply、bind的区别

由于箭头函数的this来自于继续,箭头函数无法使用以下三种方法改变this指向。
2.1call方法

(1)格式:

func.call(要改变的this指向,要给函数通报的参数1,要给函数通报的参数2, ...)
(2)特点:

传多个参数,返回值为func函数的返回值,会立刻调用函数。
(3)示例:

var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)//fn(1,2) 的时候,函数内部的 this 指向 window(函数被直接调用)
fn.call(obj, 1, 2)//fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
(4)本质:

func基于Function.prototype.call,实行call方法。实行call方法时候把func的this改为obj,并且将吸收的值作为实参通报给func函数,让function立刻实行;
2.2apply方法

(1)格式:

func.apply(要改变的 this 指向,[要给函数通报的参数1, 要给函数通报的参数2, ...])
(2)特点:

传数组,返回值为func函数的返回值,会立刻调用函数。
(3)示例:

var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)//fn(1,2) 的时候,函数内部的 this 指向 window(函数被直接调用)
fn.apply(obj, )//fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
(4)本质:

func基于Function.prototype.apply,实行apply方法。实行apply方法时候把func的this改为obj,并且将a吸收的数组中的值作为实参通报给func函数,让function立刻实行;
2.3bind方法

(1)格式:

var newFn = func.bind(obj,...params)
(2)特点:

传多个参数,返回值为bind函数的拷贝,不会立刻调用函数。
(3)示例:

var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)//fn(1, 2) 的时候 this 指向 window
newFn(1, 2)newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了obj
(4)本质:

func基于Function.prototype.bind,实行bind方法。实行bind方法时候把obj,和别的参数用闭包存储起来,并且返回原函数的拷贝(不立刻实行)。
3.手写call、apply、bind

3.1手写call

原理:使用“点”定this机制,context.xxx=self
//之前有讲过Function.prototype意味着什么
Function.prototype.myCall=function (context,...params){
    // myCall方法的this指向的是调用它的func(因为这里有小数点)
    //context指传过来的obj
    //我们需要把params中的参数传给func
    let self=this;//self指向func
    //我们需要变成self(...params),并且让self里面的this执行obj
    //如果我们能变成context.xxx(...params),这样子就可以实现
    //所以要给obj(这里也就是context)加一个属性,而且这个属性不能和obj原本有的属性冲突
   
    //这样子key就是唯一值了,不会与key原本的值发生冲突
    let key=Symbol("KEY");
    let result;

    context == null ? context=window :null;
    //如果context是基本数据类型,会报错
    //我们需要把基本数据类型转为对象类型。
    //装箱操作
    !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;

    //需要改变this的指向,把this的指向从func改为context
    context=self;
    result=context(...params);
    delete context;
    return result;
}
function func(a, b) {
console.log(this);
return a + b;
}
//不使用myCall方法
console.log(func(1, 2)); //控制台打印window,之后打印3

//使用myCall方法,传的目标不是基本数据类型
let obj = {
name: "jack",
};
console.log(func.myCall(obj, 1, 2)); //控制台先打印obj,后打印3
//使用myCall方法,传的目标是基本数据类型,如10
console.log(func.myCall(10, 1, 2));//控制台先打印10的对象,后打印3
3.2手写apply

思路和call很雷同。
Function.prototype.myApply = function (context, params) {
let self = this;

let key = Symbol("KEY");
context == null ? (context = window) : null;

!/^(object|function)$/i.test(typeof context)
    ? (context = Object(context))
    : null;

context = self;
let result;
result = context(...params);
delete context;
return result;
};
function func(a, b) {
console.log(this);
return a + b;
}
console.log(func(1, 2)); //控制台打印window,之后打印3
console.log(func.myApply(10, )); //控制台先打印10的对象,后打印12

let obj = {
name: "jack",
};
console.log(func.myApply(obj, )); //控制台先打印obj,后打印12
3.3手写bind

//手写bind方法
Function.prototype.myBind=function (context,...params){
    if (typeof this !== 'function') return console.error('Error');
    context == null ? (context=window) :null;
    //装箱操作
    !/^(object|function)$/i.test(typeof context) ? context=Object(context) : null;
    // 这里this指向调用方法func
    let self=this;
    return function proxy(...args){
      //在proxy里面需要执行func并且改变this
      //proxy里面可能也会传参数(...args)
      returnself.apply(context,params.concat(args));
    }
}
function func1() {
console.log(this);
let res = 0;
for (let i = 0; i < arguments.length; i++) {
    res = res + arguments;
}
return res;
}
setTimeout(func1.myBind(obj, 3, 4), 2000);
console.log(func1.myBind(obj, 3, 4)(5)); //12
4.思考

function func(){
    console.log(arguments)
    //argument是一个类数组,结构和操作很像数组,但数组的一些方法不能直接用,如Foreach等
    //类数组有下标,有length属性
}
如上,arguments是一个类数组。什么是类数组?


[*]拥有length属性,别的属性(索引)为非负整数
[*]不具有数组所具有的的方法;
[*]类数组是一个平凡对象,而真实的数组是Array范例
常见的类数组:


[*]函数的参数arguments;
[*]DOM对象列表(通过document.querySelectorAll)NodeList 集合;
如上,我们怎么把类数组arguments变成数组呢?


[*]Array.from(arrayLike);
Array.from()方法用于将两类对象转为真正的数组:雷同数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。后续在es6中我将详细的讲述什么是类数组、什么是可遍历的对象以及数组的一些扩展方法(如 Array.from())
[*]睁开运算符[…arrayLike];
[*]自己手写遍历
[*]…
能想到的方法有很多。我们先看一下Array.prototype中的slice方法,当它不传参数时候,会拷贝整个数组,并且把拷贝的数组返回。那么下面代码就很好理解了
let arr1 = ;
let arr2 = arr1.slice();
let arr3 = arr1;

console.log(arr2); //
console.log(arr2 === arr1); //false
console.log(arr3 === arr1); //true
假如我们自己实现slice内部的方法:
Array.prototype.mySlice = function () {
let result = [];
for (let i = 0; i < this.length; i++) {
    result.push(this);
}
return result;
};
实际上slice内部的实现思路和我们写的大同小异。再回到刚刚谁人话题,如果我们自己用for循环遍历将arguments变为数组,代码如下:
function func() {
console.log(arguments);
let result = [];
for (let i = 0; i < arguments.length; i++) {
    result.push(arguments);
}
}
观察我们自己写的循环遍历代码,和之前雷同slice内部实现的代码尴尬刁难比,会发现差别之处就是循环遍历的对象一个是arguments,一个是this。
于是我们有了一个思路,改变数组的指向,让数组指向arguments对象,如许子就可以将arguments类数组对象转为数组了。
function func() {
console.log(arguments);
let arr = [];
let result2 = arr.slice.call(arguments);
console.log(result2);
}
以是我们常常看到有很多地方常常使用[].slice.call(arguments)或者Array.prototype.slice.call(arguments)的方式把类数组转为数组。现在我们应该知道这个为什么能实现这个功能了。
由此可以得到启发,数组有很多方法,我们都可以使用这种改变this指向的方法来让类数组实行数组的一些方法来简化代码。不只是类数组,如果代码内部的结构雷同,都可以使用这种方法。如下:
function func() {
console.log(arguments);
arr.forEach.call(arguments, (item) => {
    console.log(item);//依次次打印1,2,3
});
}
func(1, 2, 3);

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: JavaScript中的this