深入探讨this

161 阅读5分钟

一、this

1、全局环境下,指向window ; 2、方法的匿名函数中:指向调用对象; 箭头函数中,指向上下文的this;

归类:

1、默认绑定:如独立函数调用

function sayHi(){
    console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();

在调用Hi()时,应用了默认绑定,this指向全局对象

2、隐式绑定:xx.fn()

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
person.sayHi(); 
Hello, YvetteLau

注意: 1、xxx.xx.fn()在判断this的时候,我们只关注最后一层 2、隐式绑定有一个大陷阱,绑定很容易丢失

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;//赋值导致this绑定丢失
Hi();

回调函数中隐式绑定丢失

function sayHi(){
    console.log('Hello,', this.name);
}
var person1 = {
    name: 'YvetteLau',
    sayHi: function(){
        setTimeout(function(){
            console.log('Hello,',this.name);
        })
    }
}
var person2 = {
    name: 'Christina',
    sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();//setTimeout的回调函数中,this使用的是默认绑定,指向全局


undefined
VM397:8 Hello, Wiliam
setTimeout(person2.sayHi,100); //person2.sayHi是隐式绑定,但是person2.sayHi赋值给setTimeOut(fn(),100)的fn,this绑定丢失
VM397:2 Hello, Wiliam
setTimeout(function(){
    person2.sayHi();//隐式绑定,this指向调用对象
},200); 
VM397:2 Hello, Christina

3、显式绑定:通过call,apply,bind的方式,显式的指定this所指向的对象

简单例子:

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)

VM429:2 Hello, YvetteLau

也会有绑定丢失情况:

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn();
}
Hi.call(person, person.sayHi); //person.sayHi赋值给了fn,相当于Hi.sayHi(),绑定丢失
 
VM438:2 Hello, Wiliam

上述情况避免:

var Hi = function(fn) {
    fn.call(this);//person.sayHi赋值给了fn,相当于Hi.sayHi(),绑定丢失,但是使用了call强制绑定到当前上下文,Hi.call(person, person.sayHi);时,this指向person
}

4、new 绑定

1、创建一个空对象,构造函数中的this指向这个空对象
2、这个新对象被执行 [[原型]] 连接
3、执行构造函数方法,属性和方法被添加到this引用的对象中
4、如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象

手写new:

function _new() {
    let target = {}; //创建的新对象
    //第一个参数是构造函数
    let [constructor, ...args] = [...arguments];
    //执行[[原型]]连接;target 是 constructor 的实例
    target.__proto__ = constructor.prototype;
    //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.apply(target, args);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
        //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
    //如果构造函数返回的不是一个对象,返回创建的新对象
    return target;
} 

这四种绑定的优先级为:

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数: 1、内部的this继承外部模块的this,不能使用call apply改变this指向 2、没有new,没有yrid命令,不能作为generator函数 3、不能使用argruments对象,

var obj = {
    hi: function(){
        console.log(this);
        return ()=>{
            console.log(this);
        }
    },
    sayHi: function(){
        return function() {
            console.log(this);
            return ()=>{
                console.log(this);
            }
        }
    },
    say: ()=>{
        console.log(this);
    }
}
let hi = obj.hi();  //隐式调用
hi(); //箭头函数中,this继承外部模块的this,即obj


VM525:3 {hi: ƒ, sayHi: ƒ, say: ƒ}
VM525:5 {hi: ƒ, sayHi: ƒ, say: ƒ}
var  sayHi = obj.sayHi();//隐式绑定,丢失,默认绑定:this指向的是全局对象window.
undefined
sayHi ()
VM525:10 Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}
()=>{
                console.log(this);
            }
let fun1 = sayHi();  //箭头函数
fun1(); //箭头函数继承外部模块的this,即window.
VM525:10 Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}
VM525:12 Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}
obj.say();  //箭头函数继承外部模块的this,即window.
VM525:17 Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}

记住:箭头函数没有自己的this,箭头函数中的this继承于外层代码库中的this.

综上:

1、函数是否在new中调用(new绑定),如果是,那么this绑定的是新创建的对象。
2、函数是否通过call,apply调用,或者使用了bind(即硬绑定),如果是,那么this绑定的就是指定的对象。
3、函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this绑定的是那个上下文对象。一般是obj.foo()
4、如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到undefined,否则绑定到全局对象。
5、如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
6、如果是箭头函数,箭头函数的this继承的是外层代码块的this。 

二、vue状态存储管理

vuex:采用集中式存贮管理应用的所有组件的状态
vuex五大核心属性:state,getter,mutation,action,module
state:存储数据,存储状态;在根实例中注册了store 后,用 this.$store.state 来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。
getter:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
action:包含任意异步操作,通过提交 mutation 间接更变状态。
module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。
为什么要用vuex?
由于传参的方法对于多层嵌套的组件将会非常繁琐 。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。
以上的这些模式非常脆弱,通常会导致代码无法维护。
所以我们需要把组件的共享状态抽取出来,以一个全局单例模式管理。
在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,
任何组件都能获取状态或者触发行为!另外,我们的代码将会变得更结构化且易维护。