从数据类型到变量、作用域、执行上下文
从数据类型到变量、作用域、执行上下文
JS数据类型
分类
1》基本类型:字符串String、数字Number、布尔值Boolean、undefined、null、symbol、bigint
2》引用类型:Object (Object、Array、Function、Date、RegExp、Error、Arguments)
Symbol是ES6新出的一种j基本数据类型,特点就是没有重复的数据,所以它可以作为object的key。
数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值
const s1 = Symbol("hi");console.log(s1); // Symbol(hi)const s2 = Symbol("hi");console.log(s1 == s2); // falselet obj = {};obj[s1] = "hello";obj[s2] = "world";const keys = Object.getOwnPropertySymbols(obj);console.log(keys); // [Symbol(hi), Symbol(hi)];
bigint大整数,不能用于Math 对象中的方法;不能和任何Number实例混合运算,两者必须转换成同一种类型
const bigNum1 = BigInt(1);const bigNum2 = BigInt(2);const num = Number(bigNum2 - bigNum1); // 1
数据类型判断
typeof
typeof检测原始类型(基本数据类型)的具体类型
返回值:数据的类型;
不能判断null的类型,如果是null,返回的是object
能判断函数的类型,函数返回的是function
console.log(typeof 666); // numberconsole.log(typeof "nb"); // stringconsole.log(typeof true); // booleanconsole.log(typeof undefined); // undefinedconsole.log(typeof null); // objectconsole.log(typeof [1, 2]); // objectconsole.log(typeof { content: "hi" }); // objectfunction func(){console.log("月亮不睡我不睡");};console.log(typeof func); // function
补充:任何实现内部使用了call方法的对象,使用typeof都返回function,比如正则表达式;在Chrome7和Safari5及之前版本,上述浏览器的正则表达式内部实现使用了call,但是后面都是返回object了
instanceof
适用于检测引用类型的具体类型
返回值:布尔值
自己定义的构造函数,new 出来的实例,也可以检测
console.log([1, 2] instanceof Array); //trueconsole.log({ content: "hi" } instanceof Object); //trueconst fn = () => {console.log("不是秃头小宝贝");};console.log(fn instanceof Function); //trueconsole.log([1, 2] instanceof Object); //trueconsole.log({ content: "hi" } instanceof Array); //falsefunction Person(name) {this.name = name;}const p = new Person("luka");console.log(p instanceof Person); //true// instanceof不能检测基本数据类型console.log(66 instanceof Number); //falseconsole.log(null instanceof Object); //false
Object.prototype.toString
const toString = Object.prototype.toString;console.log(toString.call(undefined)); // [object Undefined]console.log(toString.call(null)); // [object Null]console.log(toString.call(true)); // [object Boolean]console.log(toString.call(1)); // [object Number]console.log(toString.call("")); // [object String]console.log(toString.call({})); // [object Object]console.log(toString.call([])); // [object Array]console.log(toString.call(() => {})); // [object Function]console.log(toString.call(new Date())); // [object Date]
构造函数
对象的constructor指向创建该实例对象的构造函数
注意 null
和 undefined
没有 constructor
,以及 constructor
可以被改写,不太可靠
const arr = [1, 2, 3];console.log(arr.constructor === Array); // true
全等===
console.log(null === null);console.log(undefined === undefined);
变量
变量的存储方式
基本数据类型存在栈中,引用类型存在堆中
栈中存数据想罐子中放东西,先放的放在底下,取出的时候,最后才拿出来;栈中先进后出
堆数据结构是树状结构,从堆中拿数据就像从书架中找书
垃圾回收:基本思路就是确定变量不会再使用,就释放它的内存,这个过程是周期性的,隔一段时间回收一次
一般是函数的上下文执行完毕,函数上下文退出函数调用栈,解除变量引用,内存释放,等待垃圾回收;全局的上下文是再退出的时候才被销毁,所以我们尽量减少全局变量,也要尽量减少闭包
变量声明
var let const关键字都可以声明变量
比较三者:
1》变量提升不同;var 存在变量提升,let、const没有
console.log(num); // undefinedvar num = 1;
console.log(num); // ReferenceError: Cannot access 'num' before initializationlet num = 1;
let 在声明前使用变量会报错,也就是我们说的暂时性死区
2》作用域不同;var 声明的变量作用域在最近的上下文中(全局或函数的上下文),而let是块级作用域
var定义的age实在全局作用域中
if (true) {var age = 20;}console.log(age); // 20
if形成了块级作用域,全局作用中域访问不到就报错了
if (true) {let age = 20;}console.log(age); // ncaught ReferenceError: age is not defined
3》声明同名变量不同;var 可以在同一作用域下声明同名变量,而let不行,会报错
var str = "hihi";var str = "嗨嗨";console.log(str); //嗨嗨
let str = "hihi";let str = "嗨嗨"; // Uncaught SyntaxError: Identifier 'str' has already been declaredconsole.log(str); // 嗨嗨
4》在全局上下文中声明变量,var声明的变量会添加到window对象中(如果没有使用var来声明,直接赋值也会添加到window对象上,不会报错),而let声明的变量不会被添加到window对象中
var num1 = 20;let num2 = 22;console.log(window.num1); // 20console.log(window.num2); // undefined
const和let差不多,唯一不同的时它声明的是个常量,声明时必须赋值,声明之后,就不能重新赋值(引用类型只要不改变引用地址就行)
变量提升
一个上下文创建的时候会创建变量对象,创建变量对象的过程:
1》建立argument对象,他是一个伪数组,属性名是:0,1,……,属性值就是传入的值
2》函数提升;在变量对象中创建一个变量名为函数名,值为指向函数内存地址的引用
3》变量提升;在变量对象中以变量名建立一个属性,属性值为undefined;但是let/const声明的变量没有赋值undefined,所以不能提前使用
console.log(num);var num = 0;
相当于:
var num
console.log(num)
num=0
函数的提升先于变量的提升,首先函数的声明提升,然后变量提升
如果 var 变量与函数同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖
console.log(cal); // cal函数var cal = 10;function cal(num1, num2) {return num1 - num2;}console.log(cal); // 10
相当于:
function cal() {return num1 - num2;};var cal;console.log(cal);cal = 10;console.log(cal);
预编译
在上下文创建以后, 并不会立即执行JS代码, 而是会先进行一个预编译的过程, 根据上下文的不同, 预编译又可以分为:
- 全局预编译
- 函数预编译每个执行上下文都有一个与之相关联的变量对象 (Variable Object, 简称 VO, 初其实就是一个对象:
{key : value}
形式) , 当前执行上下文中所有的变量
和函数
都添加在其中
预编译大致过程:
1》function关键字声明的函数进行提升,声明的函数就是函数本身,但是不会执行
2》var关键字声明变量会进行提升, 属性值置为 undefined
3》如果函数名与变量名冲突(相同), 函数声明会将变量声明覆盖, 属性值就是函数本身
4》预编译结束以后, 再逐行执行代码
console.log(logA); // function logA(a){log(a)}function logA(a) {console.log(a);}var logA = 2;logA(logA); // 报错:logA is not a function
执行上下文与作用域链
执行上下文
执行上下文就是当前代码的执行环境;
上下文有全局上下文和函数上下文,函数上下文是在函数被调用的时候产生的;
var globalValue = "shake you body";function greet() {alert("hello");}greet();
JavaScript引擎会以栈的方式来处理上下文,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文
首先全局上下文进栈,greet被调用时,产生一个函数上下文,进入栈中
上下文放在栈中,栈中的先进后出,就像往一个罐子中放东西,拿出来的时候,先拿出的是罐子顶部的东西,也就是后放进去的东西,那么首先进栈的全局上下文是在栈底,最先进入,但是最后出来;函数上下文在函数被调用时产生,放入栈中,执行完后退出来;而全局上下文在程序退出前出栈
函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈
同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
全局上下文只有唯一的一个,它在浏览器关闭时出栈
函数的执行上下文的个数没有限制
每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此
上下文的生命周期:
1》创建阶段
这个阶段会创建变量对象、确定this指向,作用域链等
2》执行阶段
完成变量的赋值,执行其他js代码等
上下文在执行阶段,该上下文的变量对象VO就变为了活动对象AO
3》销毁阶段
执行上下文出栈,对应的引用失去内存空间,等待回收
变量对象
变量对象创建过程:
1》创建arguments 对象,检查当前上下文的参数,建立对应属性与属性值
2》检查当前上下文的函数声明,在变量对象中以函数名建立一个属性
3》检查当前上下文的变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,const/let 声明的变量没有赋值,不能提前使用
如果 var 变量与函数同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖
这就解释了变量提升,以及变量提升中function声明比var声明优先级高一些
作用域
作用域就是一套规则,它规定了一个变量可以使用的范围
可分为:
1》全局作用域
2》函数作用域
3》块级作用域
作用域链
作用域链,是由当前环境与上层环境的一系列变量对象组成,保证对执行环境有权访问的所有变量和函数的有序访
问
上下文创建的时候会创建变量对象arguments,并确定变量对象的作用域链
在一个执行上下文中,首先会在当前执行上下文的变量对象中查找,没有找到会往一层上下文中的变量对象上找;这种(单向)链式查找的机制被称为作用域链
var a = 20;
function test() {var b = a + 10;function innerTest() {var c = 10;return b + c;}return innerTest();
}
test();
下面代码的作用域:
content greet(全局变量对象) ----- content response (greet函数变量对象))----- str(response函数变量对象)
response变量对象中没有找到,它就往greet变量对象中找,如果greet函数的变量对象中也没有,就从全局变量对象中找
console.log(content); // hellofunction greet() {var content = "hi";console.log(content); // hifunction response() {var str = "你好";console.log(content); //hi}response();}greet();
首先全局上下文进栈,greet被调用时,产生一个函数上下文,进入栈中
上下文放在栈中,栈中的先进后出,就像往一个罐子中放东西,拿出来的时候,先拿出的是罐子顶部的东西,也就是后放进去的东西,那么首先进栈的全局上下文是在栈底,最先进入,但是最后出来;函数上下文在函数被调用时产生,放入栈中,执行完后退出来;而全局上下文在程序退出前出栈
上下文在其所有代码都执行完毕后被销毁
es6增加了块级作用域的概念:由最近的一堆{}花括号界定;if块、while块、function块
深拷贝与浅拷贝
浅拷贝
浅拷贝和深拷贝的主要区别在于是否完全独立于原始对象
浅拷贝:对于对象上的每一个属性,如果是简单类型,直接赋值值,如果是引用类型,赋值的是引用地址
const obj1 = {name: "马冬梅",more: {boyfriend: "unknow",},};const obj2 = { ...obj1 };obj1.name = "马什么梅";obj1.more.boyfriend = "none";
修改后两个对象的name不一致,boyfriend一致
实现方式
1>扩展运算符…
const arr = [1, 2, 3];const copyArr = [...arr];
2>Object.assign()
const obj1 = {name: "马冬梅",more: {boyfriend: "unknow",},};const obj2 = Object.assign({}, obj1);
const arr = [1, { name: "hello" }, 3];const copyArr = Object.assign([], arr);arr[1] = 6; // 两者索引为1的值不一样
3>Array.prototype.concat()
const arr = [1, { name: "hello" }, 3];copyArr = [].concat(arr);
4>Array.prototype.slice()
const arr = [1, { name: "hello" }, 3];copyArr = arr.slice();
深拷贝
创建一个全新的对象,新对象与原始对象具有不同的内存地址
两者相互独立,互不影响
let obj1 = { name: "马什么梅" };let obj2 = obj1;console.log(obj2); // { name: "马什么梅" }obj2.name = "马冬梅";console.log(obj1.name, obj2.name); // 马冬梅 马冬梅
实现方式
1>JSON
JSON.parse(JSON.stringify(obj))
JSON使用方便,但是它也有一些坑
当对象中有undefined类型或function类型的数据时 — undefined和function会直接丢失
const object1 = {name: undefined,fn: (v) => v,};const object2 = JSON.parse(JSON.stringify(object1));
当对象中有时间类型的元素时候 -----时间类型会被变成字符串类型数据
const object1 = {date: new Date(),};const object2 = JSON.parse(JSON.stringify(object1));console.log(typeof object1.date === typeof object2.date); // false,'object'!=='string'
当对象中有NaN、Infinity和-Infinity这三种值的时候 — 会变成null
const object1 = {isNum: NaN,};const object2 = JSON.parse(JSON.stringify(object1));console.log(object2); // { isNum: null }
当对象循环引用的时候 --会报错
const obj = {objChild: null,};obj.objChild = obj;const objCopy = JSON.parse(JSON.stringify(obj));console.log(objCopy, "objCopy"); // 报错 Converting circular structure to JSON
2>递归
const cloneDeep1 = (target, hash = new WeakMap()) => {// 对于传入参数处理if (typeof target !== 'object' || target === null) {return target;}// 哈希表中存在直接返回if (hash.has(target)) return hash.get(target);const cloneTarget = Array.isArray(target) ? [] : {};hash.set(target, cloneTarget);// 针对Symbol属性const symKeys = Object.getOwnPropertySymbols(target);if (symKeys.length) {symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);} else {cloneTarget[symKey] = target[symKey];}})}for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =typeof target[i] === 'object' && target[i] !== null? cloneDeep1(target[i], hash): target[i];}}return cloneTarget;
}
简单思路
function deepCopy(obj){//判断是否是简单数据类型,if(typeof obj == "object"){//复杂数据类型var result = obj.constructor == Array ? [] : {};for(let i in obj){result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];}}else {//简单数据类型 直接 == 赋值var result = obj;}return result;
}
相关文章:

从数据类型到变量、作用域、执行上下文
从数据类型到变量、作用域、执行上下文 JS数据类型 分类 1》基本类型:字符串String、数字Number、布尔值Boolean、undefined、null、symbol、bigint 2》引用类型:Object (Object、Array、Function、Date、RegExp、Error、Arguments) Symbol是ES6新出…...

一文读懂:AI时代到底需要什么样的网络?
各位小伙伴们大家好哈,我是老猫。 今天跟大家来聊聊数据中心网络。 提到网络,通常把网络比作高速公路,网卡相当于上下高速公路的闸口,数据包就相当于运送数据的汽车,交通法规就是“传输协议”。 如高速公路也会堵车一…...

基于HarmonyOS的宠物收养系统的设计与实现(一)
基于HarmonyOS的宠物收养系统的设计与实现(一) 本系统是简易的宠物收养系统,为了更加熟练地掌握HarmonyOS相关技术的使用。 项目创建 创建一个空项目取名为PetApp 首页实现(组件导航使用) 官方文档:组…...

严格模式报错
部分参考: Android内存泄露分析之StrictMode - 星辰之力 - 博客园 (cnblogs.com)...

nginx: [emerg] the “ssl“ parameter requires ngx_http_ssl_module in nginx.conf
nginx: [emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/nginx.conf:42 查看/usr/local/nginx/conf/nginx.conf文件第42行数据: listen 443 ssl; # server中的配置 原因是:nginx缺少 http_ssl_modul…...

Docker 部署loki日志 用于微服务
因为每次去查看日志都去登录服务器去查询相关日志文件,还有不同的微服务,不同日期的文件夹,超级麻烦,因为之前用过ELK,原本打算用ELK,在做技术调研的时候发现了一个轻量级的日志系统Loki,果断采…...

[Day 57] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
區塊鏈的零知識證明技術 一、引言 隨著區塊鏈技術的不斷發展,如何在保護用戶隱私的同時確保數據的完整性和可信度成為了研究的焦點。零知識證明(Zero-Knowledge Proof,ZKP)技術就是其中的一項關鍵技術,它允許一方在不…...

06结构型设计模式——代理模式
一、代理模式简介 代理模式(Proxy Pattern)是一种结构型设计模式(GoF书中解释结构型设计模式:一种用来处理类或对象、模块的组合关系的模式),代理模式是其中的一种,它可以为其他对象提供一种代…...

《深入浅出多模态》(九)多模态经典模型:MiniGPT-v2、MiniGPT5
🎉AI学习星球推荐: GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料,配有全面而有深度的专栏内容,包括不限于 前沿论文解读、资料共享、行业最新动态以、实践教程、求职…...

调试和优化大型深度学习模型 - 0 技术介绍
调试和优化大型深度学习模型 - 0 技术介绍 flyfish LLaMA Factory LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上…...

华为S3700交换机配置VLAN的方法
1.VLAN的详细介绍 VLAN(Virtual Local Area Network)即虚拟局域网,是一种将一个物理的局域网在逻辑上划分成多个广播域的技术。 1.1基本概念 1)作用: 隔离广播域:通过将网络划分为不同的 VLAN,广播帧只会在同一 VLAN 内传播,而不会扩散到其他 VLAN 中,从而有效…...

学懂C++(三十八):深入详解C++网络编程:套接字(Socket)开发技术
目录 一、概述与基础概念 1.1 套接字(Socket)概念 1.2 底层原理与网络协议 1.2.1 网络协议 1.2.2 套接字工作原理 二、C套接字编程核心技术 2.1 套接字编程的基本步骤 2.2 套接字编程详细实现 2.2.1 创建套接字 2.2.2 绑定地址 2.2.3 监听和接…...

SpringBoot-配置加载顺序
目录 前言 样例 内部配置加载顺序 样例 小结 前言 我之前写的配置文件,都是放在resources文件夹,根据当前目录下,优先级的高低,判断谁先被加载。但实际开发中,我们写的配置文件并不是,都放…...

第八周:机器学习笔记
第八周机器学习笔记 摘要Abstract机器学习1. 鱼和熊掌和可兼得的机器学习1.1 Deep network v.s. Fat network 2. 为什么用来验证集结果还是不好? Pytorch学习1. 卷积层代码实战2. 最大池化层代码实战3. 非线性激活层代码实战 总结 摘要 本周学习对李宏毅机器学习视…...

音乐怎么剪切掉一部分?5个方法,轻松学会音频分割!(2024全新)
音乐怎么剪切掉一部分?音频文件是娱乐和创作的重要基础。音频在我们日常生活中发挥着重要作用,从音乐播放列表到有趣的视频,它无处不在。无论是音乐爱好者还是内容创作者,我们常常需要对音频文件进行剪切和编辑。想象一下…...

洛谷 CF295D Greg and Caves
题目来源于:洛谷 题目本质:动态规划dp,枚举 解题思路:将整个洞分成两半,一半递增,一半递减。我们分别 DP 求值,最后合并。状态转移方程为:dpi,jk2∑j(j−k1)dpi−1,k1。枚举极…...

【图像处理】在图像处理算法开发中,有哪些常见的主观评价指标和客观评价指标?
主观评价指标 在图像处理算法开发中,主观评价指标依赖于观察者的个人感受和判断,通常用于评估图像的视觉质量。以下是一些常见的主观评价指标: 平均意见分数 (Mean Opinion Score, MOS):通过收集多个评价者的评分并计算平均值来评…...

从零开始学cv-6:图像的灰度变换
文章目录 一,简介:二、图像的线性变换三、分段线性变换四,非线性变换4.1 对数变换4.2 Gamma变换 五,效果: 一,简介: 图像灰度变换涉及对图像中每个像素的灰度值执行数学运算,进而调整图像的视觉…...

使用Apache POI和POI-OOXML实现word模板文档自动填充功能
最近接到一个新的需求,用户创建好模板文件保存到模板库,然后使用在线文档编辑器打开模板时,将系统数据填充到模板文件并生成新的word文件,然后在线编辑,研究使用Apache POI和POI-OOXML实现了这个功能。 Maven依赖 <…...

【HarmonyOS NEXT星河版开发学习】综合测试案例-各平台评论部分
目录 前言 功能展示 整体页面布局 最新和最热 写评论 点赞功能 界面构建 初始数据的准备 列表项部分的渲染 底部区域 index部分 知识点概述 List组件 List组件简介 ListItem组件详解 ListItemGroup组件介绍 ForEach循环渲染 列表分割线设置 列表排列方向设…...

垂直行业数字化表现抢眼 亚信科技全年利润展望乐观
大数据产业创新服务媒体 ——聚焦数据 改变商业 2024年8月14日,亚信科技控股有限公司(股票代码:01675.HK)公布了公司截至2024年6月30日的中期业绩。 财报数据显示,2024年上半年,亚信科技的营业收入为人民币…...

EmguCV学习笔记 VB.Net 4.1 颜色变换
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 教程VB.net版本请访问:EmguCV学习笔记 VB.Net 目录-CSDN博客 教程C#版本请访问:EmguCV学习笔记 C# 目录-CSD…...

【MySQL进阶之路】表结构的操作
目录 创建表 查看表 查看数据库有哪些表 查看表结构 查看表的详细信息 修改表 表的重命名 添加一列 修改某一列的属性 删除某一列 对列进行重命名 删除表 个人主页:东洛的克莱斯韦克-CSDN博客 【MySQL进阶之路】MySQL基础——从零认识MySQL-CSDN博客 创…...

3分钟搞定PDF转PPT!你一定要知道的3款转换神器!
在数字办公成为主流的当下,我们每天会收到各类基于数字化方式存储的办公文档,如PDF、PPT、Word、Excel文档等。 日常处理这些文档时,经常需要在不同格式的文档之间进行切换和转换,其中将PDF转换为PPT就是一个非常高频的需求&…...

【EasyExcel】导出excel-设置动态表头并导出数据
需求背景: 导出excel的设置某些表头动态导出(可以根据筛选条件或一些属性的数据量),方便导出后用户查看想看的信息。 一、技术选型: easyExcel的原生数据处理 二、方案设计: 根据EasyExcel支持的表头List<List<String>…...

深入探索 Elasticsearch 8:新特性与核心原理剖析(上)
深入探索 Elasticsearch 8:新特性与核心原理剖析 目录 一、引言 (二)版本 8 的重要意义 二、Elasticsearch 8 的新特性 三、Elasticsearch 的核心原理 一、引言 (一)Elasticsearch 简介 在大数据处理和搜索领域…...

瑜伽馆预约小程序,在线预约,提高商业价值
随着大众生活质量的提高,对休闲运动的关注逐渐加大,瑜伽作为一种身心放松、改善体态的运动,深受女性用户的喜爱。目前,各大瑜伽馆开始结合数字化,建立了新型的线上小程序,帮助大众快速预约体验瑜伽…...

Python--数据类型转换
在Python中,数据类型的转换是一个常见的操作,涉及将一种数据类型转换为另一种数据类型。Python提供了多种内置函数用于执行这种转换,如 int()、str()、float()、list()、tuple()、set()、dict() 等。下面详细讨论Python的基本数据类型及它们之…...

域控ntdsutil修改架构、域命名、PDC、RID、结构主机
#笔记记录# FSMO盒修改 1、提示访问特权不够,不能执行该操作,0x2098 清除缓存账号密码并修改新架构管理员账号密码即可。 背景:更替架构主机、域命名主机 C:\Windows\system32>ntdsutil ntdsutil: roles fsmo maintenance: ?? …...

解决 Swift 6 全局变量不能满足并发安全(concurrency-safe)读写的问题
概述 WWDC 24 终于在 Swift 十岁生日发布了全新的 Swift 6。这不仅意味着 Swift 进入了全新的“大”版本时代,而且 Swift 编译器终于做到了并发代码执行的“绝对安全”。 不过,从 Swift 5 一步迈入“新时代”的小伙伴们可能对新的并发检查有些许“水土不…...