参考资料web前端面试 - 面试官系列 juejin.cn/post/706741…
(一) 说一说vue2和3的区别,在created和mounted中请求数据的区别
1生命周期
| 2 | 3 | 描述 |
|---|---|---|
| beforeCreate | setup | 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务 |
| created | setup | 组件初始化完毕,各种数据可以使用,常用于异步数据获取 |
| beforeMount | onBeforeMount | 未执行渲染、更新,dom未创建 |
| mounted | onMounted | 初始化结束,dom已创建,可用于获取访问数据和dom元素 |
| beforeUpdate | onBeforeUpdate | 更新前,可用于获取更新前各种状态 |
| updated | onUpdated | 更新后,所有状态已是最新 |
| beforeDestroy | onBeforeUnmount | 销毁前,可用于一些定时器或订阅的取消 |
| destroyed | onUnmounted | 组件已销毁,作用同上 |
2多根节点
3Composition API
Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。 异步组件(Suspense) Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
4. 异步组件(Suspense)
Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
5. Teleport Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗
6. 响应式原理 Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy。
- Object.defineProperty 基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象。无法监听对象或数组新增、删除的元素。
7. 虚拟DOM Vue3 相比于 Vue2,虚拟DOM上增加 patchFlag 字段。
8. 事件缓存
Vue3 的cacheHandler可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。加一个 click 事件
9. Diff算法优化
10. 打包优化 Tree-shaking:模块打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。
created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成;mounted是在页面dom节点渲染完毕之后就立刻执行的。触发时机上created是比mounted要更早的,两者的相同点:都能拿到实例对象的属性和方法。 讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中。
(二) Vue组件之间的通信方式都有哪些?vue3 props默认值设置
- 通过 props 传递
- 通过 emit 触发自定义事件
- 使用 ref
- EventBus
- parent 或 root
- attrs 与 listeners
- Provide 与 Inject
- Vuex,pinia
// 使用 defineProps 定义 Props
const props = defineProps<{
listData?: ListItem[]; // listData 属性为可选的 ListItem 数组类型
}>();
// 使用 withDefaults 设置默认值
const props = withDefaults(
defineProps<{
listData?: ListItem[]; // listData 属性为可选的 ListItem 数组类型
}>(),
{
listData: () => [], // 设置 listData 的默认值为空数组
}
);
(三)slot插槽有几种 ,他们的区别是什么
通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理。
slot可以分来以下三种:
- 默认插槽:子组件用
<slot>标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面。 父组件在使用的时候,直接在子组件的标签内写入内容即可 - 具名插槽:子组件用
name属性来表示插槽的名字,不传为默认插槽。父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值 - 作用域插槽:子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件
v-slot接受的对象上。父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用
小结:
v-slot属性只能在<template>上使用,但在只有默认插槽时可以在组件标签上使用- 默认插槽名为
default,可以省略default直接写v-slot - 缩写为
#时不能不写参数,写成#default - 可以通过解构获取
v-slot={user},还可以重命名v-slot="{user: newName}"和定义默认值v-slot="{user = '默认值'}"
(四)WebSocket的使用,心跳检测和断点重连
var Socket = new WebSocket(url, [protocol] ); onopen 连接建立时触发 ; onmessage 客户端接收服务端数据时触发; onerror 通信发生错误时触发; onclose 连接关闭时触发 ;
data() {
return {
websocket: null, // WebSocket对象
reconnectInterval: 3000, // 重连间隔时间(毫秒)
heartbeatInterval: null, // 心跳定时器
};
},
created() {
this.setupWebSocket(); // 创建WebSocket连接
},
methods: {
setupWebSocket() {
this.websocket = new WebSocket("ws链接地址"); // 创建WebSocket连接
this.websocket.onopen = this.onWebSocketOpen; // WebSocket连接打开时的处理函数
this.websocket.onmessage = this.onWebSocketMessage; // 收到WebSocket消息时的处理函数
this.websocket.onclose = this.onWebSocketClose; // WebSocket连接关闭时的处理函数
},
closeWebSocket() {
if (this.websocket) {
this.websocket.close(); // 关闭WebSocket连接
}
},
/**
* WebSocket连接打开后,启动心跳检测
*/
onWebSocketOpen() {
console.log("WebSocket connection is open");
this.startHeartbeat();
// 发送初始化消息
const initMessage = {
type: "sub",
topic: "/aa/bb/cc/d",
parameter: {},
id: "bb",
};
this.sendMessage(JSON.stringify(initMessage));
},
// 处理从服务器接收的消息
onWebSocketMessage(event) {
if (event.data) {
const message = JSON.parse(event.data);
// 根据业务来处理数据
}
},
onWebSocketClose() {
console.log("WebSocket connection is closed");
this.stopHeartbeat(); // WebSocket连接关闭时,停止心跳检测
setTimeout(this.setupWebSocket, this.reconnectInterval); // 在一定时间后重连WebSocket
},
sendMessage(message) {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send(message); // 发送消息到WebSocket服务器
}
},
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send("ping"); // 发送心跳消息
}
}, 10000); // 每10秒发送一次心跳
},
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval); // 停止心跳检测定时器
}
},
},
beforeDestroy() {
this.closeWebSocket(); // 在组件销毁前关闭WebSocket连接
},
原文链接:https://blog.csdn.net/mi_myself/article/details/134153323
(五)js判断数据类型的方法
typeof 能准确判断出的数据类型有:Number,String,Boolean,Undefined,Symbol,BigInt,Function。它的缺点就是不能准确判断 null 的类型,而是返回 “object”。对于数组,日期,普通对象等数据,统一返回 “object”。 所以在判断基本数据类型(除了 null)和函数类型时,都会使用它。 constructor: 返回实例对象的构造函数,构造函数的原型对象上会有一个 constructor 属性,指向了构造函数自身,所以实例对象通过原型链访问 constructor 属性,就能找到自己的构造函数,也就是自己的类型了。 在平时写代码时,基本上不会用它来做数据类型的检测。
- null,undefined 没有构造函数,自然也就访问不到该属性,因此不能使用此属性来判断
- constructor 可以被改写,所以不一定准确
instanceof:沿着原型链去找 它和 constructor 一样,也是临时拉来当壮丁。它的作用是检测实例对象是不是属于某个构造函数,可以用来做数据类型的检测,实际中 instanceof 也很少用。所以它也有缺点:
- 不能检测基本数据类型
- 原型链可能被修改,导致检测结果不准确
- 只要能在原型链上找到构造函数,就返回 true,所以类型可能不准确
Object.prototype.toString:
看名字它是用来将一个值转为字符串的,但其实并不是,它是一个专门检测数据类型的方法。
它返回的值是一个形如 [object Object] 的字符串,比如:
console.log(toString.call('123')) // [object String]
console.log(toString.call(null)) // [object Null]
console.log(toString.call(true)) // [object Boolean]
console.log(toString.call({
})) // [object Object]
console.log(toString.call([])) // [object Array]
console.log(toString.call(function(){
})) // [object Function]
console.log(toString.call(new Map)) // [object Map]
console.log(toString.call(new WeakSet)) // [object WeakSet]
通常会编写一个函数,对返回的字符串从第8位做一个截取,截取到倒数第一位,再去做类型比较。 Array.isArray:专业检测数组三十年 起初以为它是 ES6 提供的新方法,后来得知其实属于 ES 5.1 规范。 看名字就知道,它是专门用于检测数组类型的,该方法的命名真实言简意赅。Array.isArray([]) // true
Number.isNaN:完善window.isNaN 这个方法就是真的属于 ES6 标准了。它能判断一个值是否严格等于NaN。 我们知道,JS 中有一个特殊的“数字” NaN,表示 not a number,不是一个数字,但它却归属于数字类型:
console.log(typeof NaN) // 'number'
NaN 用于表示不是一个数字,它不等于任何值,包括它本身。在 ES6 之前,window 对象提供了一个全局方法 isNaN,用于判断一个数字是不是 NaN:
isNaN(10) // false
isNaN('abc') // trues
isNaN(NaN) // true
可以发现,isNaN 对于字符串的检测结果也是 NaN。但这其实并不严谨,它对要判断的数据做了一个隐式类型转换,先转为数字再进行判断。而NaN 的检测应该仅限于数字类型,所以 ES6 提供了 Number.isNaN 方法:
Number.isNaN(NaN) // true
Number.isNaN('123') // false
等比较===:与固定值进行比较 直接通过与一个特定的值进行比较,从而判断数据的类型,比如:
(六)前端异步编程的方式有几种?在哪个循环中不可以使用await async
异步编程将一个函数(回调函数)作为参数传递给另一个函数,并在后者完成特定操作后调用它
- 优点:简单,易于理解
- 缺点:不利于维护、代码耦合高、使用不当容易引起内存泄漏,每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return
事件监听异步任务的执行不取决于代码的执行顺序,而取决于某个事件的触发
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”,有利于实现模块化。
- 缺点:整个程序变成事件驱动的,运行流程变得很不清晰,难以看出主流程
Promise Promise 对象表示一个异步操作的最终完成(或失败)及其结果值,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),状态不会回退
- 优点:通过链式调用
.then()和.catch()解决了回调地狱,提供了更清晰的代码结构和更好的错误处理机制。 - 缺点:代码量相对回调函数较多,需要一定的学习成本
Promise 还提供了 Promise.all(等待所有成功或任何一个失败)和 Promise.race(取最先完成的结果)等静态方法来处理多个异步操作
Generator 与 Yield
Generators 能生成一系列的值,并可暂停和恢复函数执行,通过 yield 关键字配合执行器(如 co 库)来处理异步操作
- 优点:提供了对异步流程更精细的控制能力。
- 缺点:语法较复杂,需要额外依赖执行器,现在通常被 Async/Await 取代。
Async/Await 是异步终极解决方案
在循环中
- 使用for for-of
- 避免使用
forEach和filter:forEach 不会等待异步操作,所有操作会立即启动,无法实现顺序执行。filter 方法无法在回调函数内直接实现基于异步结果的过滤。通常需要先使用map获取所有异步结果,再同步进行过滤。 - 小心使用
reduce:虽然reduce可以实现顺序执行,但需要注意在累加器中正确处理 Promise。
(七)深拷贝浅拷贝的区别?如何实现一个深拷贝?
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
在JavaScript中,存在浅拷贝的现象有:
Object.assignArray.prototype.slice(),Array.prototype.concat()- 使用拓展运算符实现的复制
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
前提为拷贝类型为引用类型的情况下:
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
(八)说说你对递归和尾递归的理解
指在函数的定义中使用函数自身的方法,在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数,
递归将函数调用简化为一个更简单的函数调用,然后再将其简化为一个更简单的函数,以此类推,直到结果
尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。
尾递归在普通尾调用的基础上,多出了2个特征:
- 在尾部调用的是函数自身。
- 可通过优化,使得计算仅占用常量栈空间。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,递归次数过多容易造成栈溢出。
这时候,我们就可以使用尾递归,即一个函数中所有递归形式的调用都出现在函数的末尾,对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。
应用场景:数组求和,使用尾递归优化求斐波那契数列,数组扁平化,数组对象格式化
(九)什么是防抖和节流?有什么区别?如何实现?
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
完成节流可以使用时间戳与定时器的写法,使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
function throttled1(fn, delay = 500) {
let oldtime = Date.now()//节流
return function (...args) {
let newtime = Date.now()
if (newtime - oldtime >= delay) {
fn.apply(null, args)
oldtime = Date.now()
}
}
}
function debounce(func, wait) {
let timeout;//防抖
return function () {
let context = this; // 保存this指向
let args = arguments; // 拿到event对象
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
(十) 大文件上传中如何做分片上传和断点续传
分片上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
断点续传
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
(十一) 数组的常用方法有哪些?
增 下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
- push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
- unshift()在数组开头添加任意多个值,然后返回新的数组长度
- splice()传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组
- concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
删 下面三种都会影响原数组,最后一项不影响原数组:
- pop()方法用于删除数组的最后一项,同时减少数组的
length值,返回被删除的项 - shift()方法用于删除数组的第一项,同时减少数组的
length值,返回被删除的项 - splice()传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
- slice()用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组
改 即修改原来数组的内容,
splice传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响
查 即查找元素,返回元素坐标或者元素值
- indexOf()返回要查找的元素在数组中的位置,如果没找到则返回 -1
- includes()返回要查找的元素在数组中的位置,找到返回
true,否则false - find()返回第一个匹配的元素
- findindex()返回第一个匹配的元素下标 排序
- reverse()顾名思义,将数组元素方向反转
- sort()方法接受一个比较函数,用于判断哪个值应该排在前面
转换
- join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
迭代(都不改变原数组)
- some()对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true
- every()对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true
- forEach()对数组每一项都运行传入的函数,没有返回值
- filter()对数组每一项都运行传入的函数,函数返回
true的项会组成数组之后返回 - map()对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
(十二)说说var、let、const之间的区别
var
- 变量提升:var 声明的变量会被提升到其作用域的顶部,这意味着可以在声明之前使用该变量,但值为 undefined。
- 全局作用域和函数作用域:在函数外部声明的 var 变量是全局变量,而在函数内部声明的 var 变量是局部变量。
- 允许重复声明:var 允许在同一作用域内多次声明同一个变量,后面的声明会覆盖前面的声明。
let
- 块级作用域:let 声明的变量只在其所在的代码块内有效。
- 不存在变量提升:在使用 let 声明变量之前,变量是不可用的,这被称为“暂时性死区”。
- 不允许重复声明:let 不允许在同一作用域内重复声明同一个变量。
const
- 声明常量:const 声明一个只读的常量,一旦声明,常量的值就不能改变。
- 必须初始化:const 声明时必须初始化。
- 块级作用域:const 和 let 一样,具有块级作用域。
(十三) 元素水平垂直居中的方法有哪些?
实现元素水平垂直居中的方式:
- 利用定位+margin:auto
- 利用定位+margin:负值
- 利用定位+transform
- table布局
- flex布局
- grid布局
(十四) css选择器有哪些?优先级?哪些属性可以继承?
内联 > ID选择器 > 类选择器 > 标签选择器
关于css属性选择器常用的有:
- id选择器(#box),选择id为box的元素
- 类选择器(.one),选择类名为one的所有元素
- 标签选择器(div),选择标签为div的所有元素
- 后代选择器(#box div),选择id为box元素内部所有的div元素
- 子选择器(.one>one_1),选择父元素为.one的所有.one_1的元素
- 相邻同胞选择器(.one+.two),选择紧接在.one之后的所有.two元素
- 群组选择器(div,p),选择div、p的所有元素
还有一些使用频率相对没那么多的选择器:
- 伪类选择器
:link :选择未被访问的链接
:visited:选取已被访问的链接
:active:选择活动链接
:hover :鼠标指针浮动在上面的元素
:focus :选择具有焦点的
:first-child:父元素的首个子元素
- 伪元素选择器
:first-letter :用于选取指定选择器的首字母
:first-line :选取指定选择器的首行
:before : 选择器在被选元素的内容前面插入内容
:after : 选择器在被选元素的内容后面插入内容
- 属性选择器
[attribute] 选择带有attribute属性的元素
[attribute=value] 选择所有使用attribute=value的元素
[attribute~=value] 选择attribute属性包含value的元素
[attribute|=value]:选择attribute属性以value开头的元素
在css中,继承是指的是给父元素设置一些属性,后代元素会自动拥有这些属性
关于继承属性,可以分成:
- 字体系列属性
font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
font-variant:偏大或偏小的字体
- 文本系列属性
text-indent:文本缩进
text-align:文本水平对刘
line-height:行高
word-spacing:增加或减少单词间的空白
letter-spacing:增加或减少字符间的空白
text-transform:控制文本大小写
direction:规定文本的书写方向
color:文本颜色
- 元素可见性
visibility
- 表格布局属性
caption-side:定位表格标题位置
border-collapse:合并表格边框
border-spacing:设置相邻单元格的边框间的距离
empty-cells:单元格的边框的出现与消失
table-layout:表格的宽度由什么决定
- 列表属性
list-style-type:文字前面的小点点样式
list-style-position:小点点位置
list-style:以上的属性可通过这属性集合
- 引用
quotes:设置嵌套引用的引号类型
- 光标属性
cursor:箭头可以变成需要的形状
继承中比较特殊的几点:
- a 标签的字体颜色不能被继承
- h1-h6标签字体的大下也是不能被继承的
无继承的属性
- display
- 文本属性:vertical-align、text-decoration
- 盒子模型的属性:宽度、高度、内外边距、边框等
- 背景属性:背景图片、颜色、位置等
- 定位属性:浮动、清除浮动、定位position等
- 生成内容属性:content、counter-reset、counter-increment
- 轮廓样式属性:outline-style、outline-width、outline-color、outline
- 页面样式属性:size、page-break-before、page-break-after
(十五)Cesium⾥⾯的Entity和primitive有什么区别
在Cesium中,Entity和primitive是两个不同的概念,它们分别⽤于不同的场景。
1、Entity
Entity是Cesium中最重要的概念之⼀,它通常⽤于描述具有坐标位置的实际对象,例如⻜机、汽⻋、楼 房、⼈物等。每个Entity实例都有不同的属性,例如位置、姿态、缩放、颜⾊、贴图等,并且可以通过编 程⽅式创建、修改、删除。
Entity的优点是⾮常灵活和易于使⽤。由于Entity是更⾼层次的概念,因此它可以⾃动处理许多与底层图 形和⼏何形状相关的复杂性,例如纹理贴图、光照、碰撞检测等。它还可以与其他Cesium组件集成,例 如事件处理、拾取和相机控制等。
2、Primitive
与Entity相⽐,Primitive是Cesium的⼀种更低层次的图形概念,它通常⽤于描述⼀组简单的图形⼏何 体。例如,我们可以使⽤Primitive来绘制点、线、多边形、体积、⽂本等基本图形。每个Primitive实例 都可以设置不同的属性,例如颜⾊、边框、宽度、⾼度等。
Primitive的主要优点是它们⾮常⾼效,因为它们使⽤了GPU硬件加速,可以轻松地绘制⼤量的⼏何体。 它们也⽐Entity更灵活,因为我们可以直接控制⼏何体的属性和细节,例如绘制线宽,选择不同的纹理贴 图等等。但Primitive的使⽤需要较少的⾃动性和灵活性,因为需要⼿动处理许多问题,例如碰撞检测、 ⾼级光照和渲染技术等。
总的来说,Entity和Primitive都是Cesium中强⼤的图形概念,但它们⽤于不同的场景。如果我们需要描 述具有复杂属性和⾏为的实际对象,那么我们应该使⽤Entity;如果我们只需要绘制简单的⼏何体,那么 我们可以使⽤Primitive来获得更好的性能和控制。