深入了解该指向 [JavaScript]。
在JavaScript中,this
是一个特殊的关键字,用于引用函数调用的上下文对象。理解this
的指向对于掌握JavaScript至关重要。它的值在不同的情况下可能会有所不同。
一、常见的场景下的this指向
1. 全局上下文
在全局上下文中(在没有任何函数内),this
指向全局对象。在浏览器环境中,this
指向window
对象。
console.log(this === window); // true
2. 函数调用
在普通函数调用中,this
指向全局对象(在严格模式下为undefined
)。
function showThis() {
console.log(this);
}
showThis(); // 在浏览器中, 输出 window 对象
在严格模式下:
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined
3. 方法调用
当一个函数作为对象的方法被调用时,this
指向该对象。
const obj = {
name: 'Alice',
showName: function() {
console.log(this.name);
}
};
obj.showName(); // Alice
4. 构造函数
当用new
关键字调用构造函数时,this
指向新创建的实例。
function Person(name) {
this.name = name;
}
const p1 = new Person('Bob');
console.log(p1.name); // Bob
5. 箭头函数
箭头函数不绑定自己的this
,它的this
是从定义它的上下文中继承而来。这意味着箭头函数的this
与其外部函数的this
相同。
const obj = {
name: 'Charlie',
getName: function() {
const innerFunc = () => {
console.log(this.name);
};
innerFunc();
}
};
obj.getName(); // Charlie
6. call
、apply
和 bind
这三个方法可以显式地设置函数内的this
值:
-
call
:接受多个参数,第一个参数为this
的值,后面的参数为函数的参数。
function greet() {
console.log(`Hello, ${this.name}`);
}
const person = { name: 'Dave' };
greet.call(person); // Hello, Dave
-
apply
:与call
类似,但第二个参数是一个数组。
function greet(greetWord) {
console.log(`${greetWord}, ${this.name}`);
}
greet.apply(person, ['Hi']); // Hi, Dave
-
bind
:返回一个新函数,永久绑定了指定的this
值。
const boundGreet = greet.bind(person);
boundGreet(); // Hello, Dave
7. 事件处理程序
在事件处理程序中,this
通常指向触发事件的元素。
document.getElementById('myButton').addEventListener('click', function() {
console.log(this); // <button id="myButton">...</button>
});
8. 模块中的this
在以模块方式(使用import
和export
)编写的JavaScript代码中,*this
是undefined
。
console.log(this); // undefined (在模块中)
二、从绑定方式入手,介绍this指向
1. 默认绑定
定义: 在全局上下文中,this
默认指向全局对象(在浏览器中是 window
,在 Node.js 环境中是 global
)。
示例:
function show() {
console.log(this); // 在浏览器中指向 window
}
show(); // 输出: Window { ... }
2. 隐式绑定
定义: 当一个函数作为对象的方法被调用时,this
指向调用该方法的对象。
示例:
const person = {
name: 'John',
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // 输出: Hello, my name is John
3. 显式绑定
定义: 通过 call()
、apply()
或 bind()
显式地指定 this
的值。
示例:
function greet()) {
console.log(`Hello, my name is ${this.name}`);
}
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
greet.call(person1); // 输出: Hello, my name is Alice
greet.apply(person2); // 输出: Hello, my name is Bob
const greetBob = greet.bind(person2);
greetBob(); // 输出: Hello, my name is Bob
4. new 绑定
定义: 当一个函数作为构造函数调用时,使用 new
关键字创建的新对象,将作为 this
的值。
示例:
function Person(name) {
this.name = name;
}
const person = new Person('Charlie');
console.log(person.name); // 输出: Charlie
隐式丢失
在某些情况下,this
的绑定可能会丢失,造成其指向不正确。以下是一些常见的情况:
-
函数别名:
const person = { name: 'David', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; const greet = person.greet; greet(); // 输出: Hello, my name is undefined(默认为全局对象)
-
函数作为参数传递:
const person = { name: 'Eva', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; setTimeout(person.greet, 1000); // 输出: Hello, my name is undefined
-
内置函数:
const person = { name: 'Frank', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; const greetMethod = person.greet.toString(); console.log(greetMethod); // 这个操作并不会改变 this 的指向
-
间接调用:
const person = { name: 'Grace', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; const greet = person.greet; const fn = () => greet(); fn(); // 输出: Hello, my name is undefined
三、从函数调用的角度看待this指向
函数的调用有多种方式,每种调用方式在 JavaScript 中具有不同的行为和上下文(this
的指向)。下面是对四种主要调用方式的简要介绍:
1. 独立调用(Function Invocation)
- 定义:这是最简单的函数调用方式,直接调用函数时的形式。
-
例子:
function greet() { console.log("Hello!"); } greet(); // 独立调用
-
注意:在独立调用下,
this
的值是全局对象(在浏览器中是window
),如果在严格模式下('use strict';
),this
是undefined
。
2. 方法调用(Method Invocation)
- 定义:当函数作为对象的方法被调用时,称为方法调用。
-
例子:
const person = { name: "Alice", greet: function() { console.log("Hello, " + this.name); } }; person.greet(); // 方法调用
-
注意:在这种情况下,
this
指向调用方法的对象(在例子中是person
对象)。
3. 间接调用(Indirect Invocation)
-
定义:使用
call()
和apply()
方法来调用函数,即使函数本身不属于某个对象。 -
例子:
function greet() { console.log("Hello, " + this.name); } const person = { name: "Alice" }; greet.call(person); // 使用 call() 进行间接调用 greet.apply(person); // 使用 apply() 进行间接调用
-
注意:
-
call()
接受的第一个参数是this
的值,后面可以跟随参数。 -
apply()
同样接受第一个参数为this
的值,第二个参数是一个数组,代表传入的参数。
-
4. 构造函数调用(Constructor Invocation)
-
定义:当使用
new
关键字调用函数时,该函数被视为构造函数,专用于创建对象。 -
例子:
function Person(name) { this.name = name; } const alice = new Person("Alice"); // 构造函数调用 console.log(alice.name); // "Alice"
-
注意:在构造函数中,
this
指向新创建的对象。《构造函数调用》是一种特殊类型的函数调用,通常用于初始化新对象。
总结
this
的指向与函数的调用方式密切相关。通过掌握默认绑定、隐式绑定、显式绑定和构造函数绑定这四种不同的形式,可以有效地理解和控制 this
的行为。隐式丢失是一个常见的陷阱,需要特别注意,以避免出现指向错误的结果。
推荐阅读
-
深入了解该指向 [JavaScript]。
-
深入了解JS混淆加密及其特点:保护你的JavaScript代码
-
深入了解前端JavaScript加密相关知识
-
解密多文件混淆技术:带你深入了解该主题
-
深入了解JavaScript混淆技术的核心原理和常见方法
-
深入了解JavaScript类(class)与对象(Object),详细探索原型与类的关系
-
深入了解 Javascript 中构造函数和原型对象的区别
-
深入了解 JavaScript 原型:原型、__proto____ 和构造函数
-
深入了解 JavaScript 中的匿名函数
-
go语言Socket编程-Socket编程 什么是Socket Socket,英文含义是插座、插孔,一般称之为套接字,用于描述IP地址和端口。可以实现不同程序间的数据通信。 Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket,该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。 套接字的内核实现较为复杂,不宜在学习初期深入学习,了解到如下结构足矣。 套接字通讯原理示意 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。 常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 网络应用程序设计模式 C/S模式 传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各自部署客户机和服务器来完成数据通信。 B/S模式 浏览器(Browser)/服务器(Server)模式。只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。 优缺点 对于C/S模式来说,其优点明显。客户端位于目标主机上可以保证性能,将数据缓存至客户端本地,从而提高数据传输效率。且,一般来说客户端和服务器程序由一个开发团队创作,所以他们之间所采用的协议相对灵活。可以在标准协议的基础上根据需求裁剪及定制。例如,腾讯所采用的通信协议,即为ftp协议的修改剪裁版。 因此,传统的网络应用程序及较大型的网络应用程序都首选C/S模式进行开发。如,知名的网络游戏魔兽世界。3D画面,数据量庞大,使用C/S模式可以提前在本地进行大量数据的缓存处理,从而提高观感。 C/S模式的缺点也较突出。由于客户端和服务器都需要有一个开发团队来完成开发。工作量将成倍提升,开发周期较长。另外,从用户角度出发,需要将客户端安插至用户主机上,对用户主机的安全性构成威胁。这也是很多用户不愿使用C/S模式应用程序的重要原因。 B/S模式相比C/S模式而言,由于它没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小。只需开发服务器端即可。另外由于其采用浏览器显示数据,因此移植性非常好,不受平台限制。如早期的偷菜游戏,在各个平台上都可以完美运行。 B/S模式的缺点也较明显。由于使用第三方浏览器,因此网络应用支持受限。另外,没有客户端放到对方主机上,缓存数据不尽如人意,从而传输数据量受到限制。应用的观感大打折扣。第三,必须与浏览器一样,采用标准http协议进行通信,协议选择不灵活。 因此在开发过程中,模式的选择由上述各自的特点决定。根据实际需求选择应用程序设计模式。 简单的C/S模型通信 Server端:Listen函数 func Listen(network, address string) (Listener, error) network:选用的协议:TCP、UDP, 如:“tcp”或 “udp” address:IP地址+端口号, 如:“127.0.0.1:8000”或 “:8000” Listener 接口: type Listener interface { Accept (Conn, error) Close error Addr Addr } Conn 接口: type Conn interface { Read(b byte) (n int, err error) Write(b byte) (n int, err error) Close error LocalAddr Addr RemoteAddr Addr SetDeadline(t time.Time) error SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error } 参看 [<u>https://studygolang.com/pkgdoc</u>](https://studygolang.com/pkgdoc) 中文帮助文档中的demo: 示例代码:TCP服务器.go package main import ( "net" "fmt" ) func main { // 创建监听 listener, err:= net.Listen("tcp", ":8000") if err != nil { fmt.Println("listen err:", err) return } defer listener.Close // 主协程结束时,关闭listener fmt.Println("服务器等待客户端建立连接...") // 等待客户端连接请求 conn, err := listener.Accept if err != nil { fmt.Println("accept err:", err) return } defer conn.Close // 使用结束,断开与客户端链接 fmt.Println("客户端与服务器连接建立成功...") // 接收客户端数据 buf := make(byte, 1024) // 创建1024大小的缓冲区,用于read n, err := conn.Read(buf) if err != nil { fmt.Println("read err:", err) return } fmt.Println("服务器读到:", string(buf[:n])) // 读多少,打印多少。 }