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, [1, 2])//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[key]=self;
- result=context[key](...params);
- delete context[key];
- 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[key] = self;
- let result;
- result = context[key](...params);
- delete context[key];
- return result;
- };
- function func(a, b) {
- console.log(this);
- return a + b;
- }
- console.log(func(1, 2)); //控制台打印window,之后打印3
- console.log(func.myApply(10, [5, 7])); //控制台先打印10的对象,后打印12
- let obj = {
- name: "jack",
- };
- console.log(func.myApply(obj, [5, 7])); //控制台先打印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)
- return self.apply(context,params.concat(args));
- }
- }
- function func1() {
- console.log(this);
- let res = 0;
- for (let i = 0; i < arguments.length; i++) {
- res = res + arguments[i];
- }
- 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 = [2, 3, 4];
- let arr2 = arr1.slice();
- let arr3 = arr1;
- console.log(arr2); //[2,3,4]
- 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[i]);
- }
- return result;
- };
复制代码 实际上slice内部的实现思路和我们写的大同小异。再回到刚刚谁人话题,如果我们自己用for循环遍历将arguments变为数组,代码如下:
- function func() {
- console.log(arguments);
- let result = [];
- for (let i = 0; i < arguments.length; i++) {
- result.push(arguments[i]);
- }
- }
复制代码 观察我们自己写的循环遍历代码,和之前雷同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企服之家,中国第一个企服评测及商务社交产业平台。 |