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>