JS17 - this 指向 - 实例 - 移动端选项卡功能

253 阅读7分钟

this 关键字 - 指向对象

  • 本质:当前执行上下文(global、function、object等)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

  • 指向调用:this 代表的是函数运行时所在的对象,因此,不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同,可以理解为谁调用 this,this 就指向谁(箭头函数特例)

//例如,这里的this在对象test的函数中,当执行函数的时候,是test调用的,因此this便指向test对象
const test = {
    prop:42,
    func:function(){
        console.log(this.prop);
    }
}
test.func();    //42

默认 this 指向

全局调用

  • this - 指向全局:无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。
  • globalThis - 核心:全局属性 globalThis 包含全局的 this 值,类似于全局对象(global object),可以简单的认为,全局作用域中的 this 就是 globalThis
  • 全局对象获取问题:在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window、self 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global。
  • this指向全局问题:在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。也可以使用 Function('return this')(),但那些禁用eval()的环境,如在浏览器中的CSP,不允许这样使用Function。
  • globalThis - 万用:globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,可以安心的使用 globalThis,不必担心它的运行环境。
//在浏览器中,window对象同时也是全局对象
console.log(this === window);       //true
console.log(globalThis === this);   //true
console.log(globalThis === window); //true

//声明一个全局变量
var a = 40;
console.log(this.a);        //40
console.log(globalThis.a);  //40
//注意:使用let声明的不是window属性
let b = 60;
console.log(window.b);      //undefined
console.log(this.b);        //undefined
console.log(globalThis.b);  //undefined

//在全局变量中,可以直接使用this代表window
this.num = 80;
console.log(num);           //80
console.log(this.num);      //80
console.log(window.num);    //80
console.log(globalThis.num);//80、

//setTimeout() 是window的方法,因此这里的this依然是window调用的
setTimeout(function(){
    console.log(this);
}, 500);

对象调用

如果直接调用this的对象不是window,而是其他对象,那么this便会指向调用它直接对象

var number = 90;
//自定义对象,this指向的是自定义的对象
var customObj = {
    name:"James",
    number:80,
    prop:function(){
        console.log(this.number);
    }
}
customObj.prop();           //80
//虽然customObj是window的全局属性,但直接调用this的还是自定义对象
window.customObj.prop();    //80
//如果把customObj的函数拿出来,赋值给全局变量,让window调用,那么this就会指向window
let func = customObj.prop;
func();                     //90

//事件绑定的this
/**以click事件为例:
 * 执行注册在box这个元素上的click事件,
 * 相当于是执行 box.onclick(),function 则是回调函数,
 * 那么function中的this指向的便是调用它的那个对象,
 * 而此处在调用的对象便是,box元素对象
 */ 
box.onclick = function(evt){
    console.log(this);                  //被点击的元素对象
    console.log(this === evt.target);   //true
}
/**this 与 evt.target 的区别:
 * this --> 指向的永远只有起初调用的那一个,不会因为事件流改变
 * evt.target --> 会因为事件流该表,而指向与之绑定的那个元素对象
 */
container.addEventListener("click",function(evt){
    console.log(this);
    console.log(this === evt.target);
});

函数调用

在函数内部,this的值取决于函数被调用的方式,懒散模式的this默认指向全局,严格模式需如果在函数内不设置,this值为undefined

//严格模式 - 不设置this
function strictModeNone(){
    "use strict";   //定义函数环境为严格模式
    return this;
}
console.log(strictModeNone());  //undefined

//严格模式 - 设置this
/**设置this的时候,注意this是一个关键字,不能通过关键字赋值的方式设置
 * 要理解this的本质,是一个对象的代表,
 * 因此,需要将包含this的代码放在某个对象的属性或方法中,让this指向它
 */ 
let specialObj = {
    o:"object",
    prop:function strictModeSetted(){
    "use strict";
    return this;
    }
}
console.log(specialObj.prop()); //specialObj对象 {o: "object", prop: ƒ}

//懒散模式 sloppy mode
/**函数内 this 的值不是由该函数设置的,
 * 所以 this 的值默认指向全局对象,浏览器中就是 window
 */
function sloppyMode(){
    return this;
}
console.log(sloppyMode());        //window对象

类调用

由于类本质上是函数,所以 this 在类中的表现与在函数中类似,区别在于:

  • 在类的构造函数中,this 是一个常规对象
  • 类中所有非静态的方法都会被添加到 this 的原型中
  • 派生类:不像基类的构造函数,派生类的构造函数没有初始的 this 绑定,在构造函数中调用 super() 会生成一个 this 绑定,并相当于执行代码 this = new Base() --> this不可作为变量名使用,在调用super()之前引用this会抛出错误
class Example {
  constructor() {
    const proto = Object.getPrototypeOf(this);
    console.log(Object.getOwnPropertyNames(proto));
  }
  first(){}
  second(){}
  static third(){}
}
new Example(); // ['constructor', 'first', 'second']

自定义 this 指向

作为某个对象的代表,this 常常是默认指定的,但如果需要把 this 的指向从一个对象传到另一个,可以使用 call、apply、bind 方法

  • 共同功能:在 call() apply() bind() 被调用时,this 分别被指定为 call() apply() bind() 的第一个参数/新对象(非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装),而其余参数将作为新函数的参数,供调用时使用
  • 主要区别
    • call 使用单个一系列参数,马上执行函数
    • apply 使用参数数组而不是一组参数列表,马上执行函数
    • bind 使用单个一系列参数,但不马上执行,而是返回一个函数,等待调用执行
  • 应用场景:在调用一个存在的函数时,你可以为其指定一个 this 对象。this 指当前对象,也就是正在调用这个函数的对象。使用 apply,可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。使用 bind,可以当作一个函数被两个对象使用,实现了函数方法的拷贝,ES5 引入了 bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。

call() 方法

  • 语法function.call(thisArg, arg1, arg2, ...)
  • 参数arg1, arg2, ... 指定的参数列表
  • 结果:this指向新对象,函数立即执行,返回原函数return结果

apply() 方法

  • 语法apply(thisArg, argsArray)
  • 参数argsArray 数组/类数组
  • 结果:this指向新对象,函数立即执行,返回原函数return结果

bind() 方法

  • 语法function.bind(thisArg, arg1, arg2, ...)
  • 参数arg1, arg2, ... 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
  • 结果:返回一个原函数的拷贝,并拥有指定的this值(对象)和初始参数
var sname = "CEO";

let primaryObj = {
    sname:"James",
    age:25,
    work:"developer",
    prop:function(sname,age){
        console.log(`name:${sname} age:${age} work:${this.work}`)
        return this;
    }
}

let seniorObj = {
    sname:"Bond",
    age:50,
    work:"scientist",
    prop:function(){
        //none...
    }
}

/* 默认:this指向 */
console.log(primaryObj.prop(primaryObj.sname,primaryObj.age));  
//输出:name:James age:25 work:developer
//输出:{sname: "James", age: 25, work: "developer", prop: ƒ}
/**注意形参:
 * 如果 this 写在函数形参那里,就不是指向调用函数的那个对象,
 * 而是外面一层代码调用的,此处的外层就是window,
 * this.sname 就成了 window.sname,形参需要用对象名
 * 例如:
 * console.log(primaryObj.prop(this.sname,this.age));
 *      输入结果就是://name:CEO age:undefined work:developer
 */

/* 自定义:让this指向新对象seniorObj */
//call 方法
let resultCall = primaryObj.prop.call(seniorObj, seniorObj.sname, seniorObj.age); 
//输出(立即执行函数):name:Bond age:50 work:scientist
console.log(resultCall);                                    
//输出(原函数return的结果,此处输出新对象,是因为this指向了新对象):
//{sname: "Bond", age: 50, work: "scientist", prop: ƒ}age: 50prop: ƒ ()sname: "Bond"work: "scientist"__proto__: Object

//apply 方法
let resultApply = primaryObj.prop.apply(seniorObj, [seniorObj.sname,seniorObj.age]);
//输出(立即执行函数):name:Bond age:50 work:scientist
console.log(resultApply);
//输出(原理同上):{sname: "Bond", age: 50, work: "scientist", prop: ƒ}age: 50prop: ƒ ()sname: "Bond"work: "scientist"__proto__: Object

//bind 方法
let resultBind = primaryObj.prop.bind(seniorObj, seniorObj.sname, seniorObj.age);
console.log(resultBind);
//bind() 方法,不会立即执行函数,而是返回原函数的一个拷贝
//因此输出:
//   ƒ (sname,age){
//       console.log(`name:${sname} age:${age} work:${this.work}`)
//       return this;
//   }
console.log(resultBind());
//输出:(此时,就跟 call 和 apply 执行结果一致了)
//    name:Bond age:50 work:scientist
//    {sname: "Bond", age: 50, work: "scientist", prop: ƒ}
function sayHi(){
    console.log("Hello ",this.sname);

}
var per_1 = {
    sname:"James",
    sayHi:function(){
        setTimeout(()=>{
            console.log("Hi ",this.sname);
        },100)
    }
}
var per_2 = {
    sname:"Joker",
    sayHi:sayHi
}
var sname = "John";
//输出结果
per_1.sayHi();                  // Hi John
setTimeout(per_2.sayHi,100);    // Hello John
setTimeout(()=>{
    per_2.sayHi();
},100);                         // Hello Joker

实例

手写 new 关键字

/**手写 new 关键字:
 * 了解 new 关键字的作用:创建具有 构造函数的内置对象 的实例
 * step 1: 创建实例化对象
 * step 2: 指向构造函数
 * step 3: 具有该构造函数的内置对象 -> 原型链指向构造函数原型
 */
function cusNew(){
    //1.创建实例化对象 -> 因此需要一个空对象承接
    let obj = {};
    //2.抽取第一个参数(要求传入的第一个参数为 构造函数)
    let fn = [].shift.call(arguments);  //让类数组arguments使用数组的方法shift()
    //3.让 实例化对象的原型链 指向 构造函数的原型
    obj.__proto__ = fn.prototype;
    //4.改变this指向,让 实例化的对象 调用构造函数的内置方法和属性
    fn.apply(obj,arguments);    //替代 fn(arguments) -> 即替代 [构造函数].(arguments)
    return obj;
}
//构造函数
function Student(sname,age,study){
    this.sname = sname;
    this.age = age;
    this.study = study;
}
//参数
let sname = "James";
let age = 18;
let study = _ => "take class.";
//执行方法cusNew,作用与new关键字一样
let result_1 = cusNew(Student,sname,age,study);     
let result_2 = new Student(sname,age,study);        
console.log(result_1);                              //Student{"James",18,study: ƒ}
console.log(result_2);                              //Student{"James",18,study: ƒ}

选项卡功能

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html,body{
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            font-size: 20%;
            overflow: auto;
        }
        .container{
            height: 90%;
            margin: 0.2rem;
            border: 1px solid black;
        }
        .navigator{
            width: 100%;
            height: 10%;
            color: white;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
            flex: auto;
        }
        .navigator > div{
            width: 100%;
            height: 100%;
            background-color: black;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
        }
        .navigator > div:nth-child(1){
            background-color: white;
            color: black;
        }
        .content{
            width: 100%;
            height: 80%;
            margin: 0;
            padding: 0;
            list-style-type: none;
            position: relative;
        }
        .content li{
            height: 100%;
            position:absolute;
            padding: 0.2rem;
            border-top: 0;
            display: none;
            text-align: center;
        }
        .content > li:nth-child(1){
            display: block;
        }
    </style>
</head>
<body>
    <section id="container" class="container">
        <nav id="navigator" class="navigator">
            <div>1</div>
            <div>2</div>
            <div>3</div>
        </nav>
        <ul id="content" class="content">
            <li>1<br>Lorem ipsum dolor sit amet consectetur adipisicing elit. Corrupti vitae reprehenderit atque blanditiis officia reiciendis inventore omnis ad! Reiciendis numquam rem nulla asperiores ut recusandae magnam dolores est officia nemo.</li>
            <li>2<br>Lorem ipsum dolor sit amet consectetur adipisicing elit. Corrupti vitae reprehenderit atque blanditiis officia reiciendis inventore omnis ad! Reiciendis numquam rem nulla asperiores ut recusandae magnam dolores est officia nemo.</li>
            <li>3<br>Lorem ipsum dolor sit amet consectetur adipisicing elit. Corrupti vitae reprehenderit atque blanditiis officia reiciendis inventore omnis ad! Reiciendis numquam rem nulla asperiores ut recusandae magnam dolores est officia nemo.</li>
        </ul>
    </section>
</body>
<script>
    //计算fontSize适配移动段 - 以375px为基准,同时设置fontSize为100px基础
    document.documentElement.style.fontSize = document.documentElement.clientWidth / 375 * 100 + "px";
    let btns = document.querySelectorAll("#navigator > div");
    let tabs = document.querySelectorAll("#content > li");
    //按键切换
    Array.from(btns).forEach(function(item,index){
        item.addEventListener("touchstart",function(){
            Array.from(btns).forEach((ele,i) =>  {
                ele.style.backgroundColor = "black"
                ele.style.color = "white";
                ele.removeAttribute("id");
                tabs[i].style.display = "none";
            });    
            this.style.backgroundColor = "white";
            this.style.color = "black";
            //内容页与按键同步切换
            tabs[index].style.display = "block";
        });
    })
</script>
</html>

移动短选项卡切换.gif