(超详细版)2019北京面试题————Vue

一、Vue解决了什么问题

虚拟dom:dom操做时很是耗性能的,再也不使用原生的dom操做节点,极大的解放dom操做,但具体操做的仍是dom,不过是换了一种方式。
视图、数据、结构分离:使数据的更改更为简单,不须要进行逻辑代码的修改,只须要操做数据就能完成相关操做。
组件化:把一个单页应用中的各类模块拆分到一个一个单独的组件中,便于开发,以及后期的维护

二、MVVM的理解

     MVVM就是Model-View-ViewModel的缩写,MVVM将视图和业务逻辑分开。
     View:视图层,Model数据模型,而ViewModel是把二者创建通讯的桥梁。
     在MVVM框架下,View和Model之间没有直接的联系,而是经过ViewModel进行交互。View和ViewModel之间以及Model和ViewModel之间的交互都是双向的,所以view数据的变化会同步到Model中,而Model数据的变化也会当即反映到View上。能够说它们二者是实时更新的,互相影响。  ViewModel经过双向数据绑定把View层和Model层链接了起来,而View和Model之间的同步工做彻底是自动的,所以开发者只须要关注业务逻辑,不须要手动操做DOM,也不须要关注数据状态的同步问题,这些都由MVVM统一管理。

三、如何实现一个自定义组件,不一样组件之间是如何通讯的

请永远牢记vue是单向数据流javascript

自定义组件:css

      建立子组件的文件,创建组件的模板,把架子搭起来,也就是在子组件中写好<template>视图层,<script>逻辑层<style>css样式层。而后定义好props里面的数据,实现子组件须要的逻辑代码后,也就封装好了,而后直接调用便可。调用的花import引入,同时在父组件<script>(逻辑层)中的components这个对象中写入组件名称,最后挂载到父组件的template中便可。html

组件通讯:前端

①props / $emitvue

父组件经过props的方式向子组件传递数据,而经过$emit子组件能够向父组件通讯。java

②$children / $parentnode

this.$children[0].msg = "hello world" //父组件修改子组件data中的数据
this.$parent.mag //子组件拿到父组件data中的数据

$children的值是数组,$parent的值是个对象复制代码

注意:$parent,$children它们的目的是做为访问数组的应急方法,更推荐用props和events实现父子组件通讯。react

③provide / injectwebpack

这是vue2.2.0新增的api,简单来讲就是父组件中经过provide来提供变量,而后再子组件中经过inject来注入变量。ios

//父组件
export default{
    name:"A",
    provide:{
        for:"demo"
    },
    components:{
        comB
    }
}

//子组件
export default{
    name:"B",
    inject:["for"],
    data(){
        demo:this.for
    }
}复制代码

④ref / refs

ref:若是在普通的DOM元素上使用,引用指向的就是DOM元素;若是用在子组件上,引用就指向组件实例,可经过实例直接调用组件的方法或访问数据。

//父组件
<template>
    <component-a ref="comA"></component-a>
</template>
<script>
    export default{
        mounted(){
            const comA = this.$refs.comA;
            console.log(comA.name)//Vue.js
            comA.sayHello() //hello
        }
    }
</script>复制代码

//子组件
export default{
    data(){
        return {
            name:"Vue.js"
        }
    },
    methods:{
        sayHello(){
            console.log("hello")
        }
    }
}复制代码

⑤eventBus(Bus总线):

//首先在src中建立一个Bus文件夹 => index.js
import Vue from "vue";
export default new Vue({
    
})

//子组件1(发送数据的组件)
<button @click="add()">点击</button>
import Bus from "@/Bus"
add(){
    Bus.$emit("add",this.content);
}

//子组件2(接受数据的组件)
<p>{{tit}}</p>
import Bus from "@/Bus";
created(){
    Bus.$on("add",(data) => {
        this.tit = data;
    })
}复制代码

⑥Vuex;

⑦LocalStorage;

⑧$attrs / $listeners

     将数据挂在到子组件的标签上去后,在子组件中使用this.$attrs直接获取到全部挂载的数据,返回的是一个对象。 

四、nextTick的理解

使用nextTick的缘由:Vue是异步修改DOM的,而且不鼓励开发者直接接触DOM,可是有时候须要必须对数据更改后的DOM元素作相应的处理,可是获取到的DOM数据并非更改后的数据,这时候就须要this.$nextTick();

原理:Vue经过异步队列控制DOM更新和nextTick回调函数前后执行的方式。

使用:

//HTML
<button @click="change()">按钮</button><h1 ref="gss">{{msg}}</h1>
//JS
export default{
    name:"app",
    data(){
        return {
            msg:"123"
        }
    },
    methods:{
        change(){
            this.msg = "456";
            console.log(this.refs["gss"].innerHTML)//123
            this.$nextTick(function(){
                console.log(this.refs["gss"].innerHTML)//456
            })
        }
    }
    
}

复制代码

五、Vue的生命周期(11个钩子函数)

⑴beforeCreate(建立前):在今生命周期函数执行的时候,data和methods中的数据都尚未初始化。
⑵created(建立后):在今生命周期函数中,data和methods都已经被初始化好了,若是要调用 methods中的方法,或者操做data中的数据,最先只能在created中操做。
⑶beforeMount(载入前):在今生命周期函数执行的时候,模板已经在内存中编译好了,可是还没有挂载到页面中去,此时页面仍是旧的。
⑷mounted(载入后):此时页面和内存中都是最新的数据,这个钩子函数是最先能够操做dom节点的方法。
⑸beforeUpdate(更新前):此时页面中显示的数据仍是旧的,可是data中的数据是最新的,且页面并未和最新的数据同步。
⑹Updated(更新后):此时页面显示数据和最新的data数据同步。
⑺beforeDestroy(销毁前):当执行该生命周期函数的时候,实例身上全部的data,全部的methods以及过滤器......等都处于可用状态,并无真正执行销毁。
⑻destroyed(销毁后):此时组件以及被彻底销毁,实例中的全部的数据、方法、属性、过滤器......等都已经不可用了。
//下面两个钩子函数通常配合<keep-alive></keep-alive>使用
⑼activated(组件激活时):和上面的beforeDestroy和destroyed用法差很少,可是若是咱们须要一个实例,在销毁后再次出现的话,用beforeDestroy和destroyed的话,就太浪费性能了。实例被激活时使用,用于重复激活一个实例的时候
⑽deactivated(组件未激活时):实例没有被激活时。
⑾errorCaptured(错误调用):当捕获一个来自后代组件的错误时被调用

六、虚拟DOM原理

         虚拟DOM,其实就是用对象的方式取代真实的DOM操做,把真实的DOM操做放在内存当中,在内存中的对象里作模拟操做。当页面打开时浏览器会解析HTML元素,构建一颗DOM树,将状态所有保存起来,在内存当中模拟咱们真实的DOM操做,操做完后又会生成一颗dom树,两颗DOM树进行比较,根据diff算法比较两颗DOM树不一样的地方,只渲染一次不一样的地方。

补充:

diff算法核心:
①如何用vnode生成一个dom的节点

patch方法patch(container, vnode)patch(vnode, newVnode)复制代码

②vnode和newVnode的对比
③修改改变的dom节点

replacechildrencreateElement复制代码

七、双向绑定的原理?数据劫持?

       vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式。
        经过Object.defineProperty()来劫持各个属性的setter,getter,在数据变更时发布消息给订阅者,触发相应监听回调。
        当把一个普通 Javascript 对象传给 Vue 实例来做为它的 data 选项时。Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,可是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
         vue的数据双向绑定 将MVVM做为数据绑定的入口,整合Observer,Compile和Watcher三者。经过Observer来监听本身的model的数据变化,经过Compile来解析编译模板指令(vue中是用来解析 {{}})。最终利用watcher搭起observer和Compile之间的通讯桥梁,达到数据变化 —>视图更新;

数据劫持:当咱们访问或设置对象的属性的时候,都会触发相对应的函数,而后在这个函数里返回或设置属性的值。咱们能够在触发函数的时候动一些手脚作点咱们本身想作的事情,这也就是“劫持”操做

八、Proxy相比于defineProperty的优点

Vue3.0摒弃了Object.defineProperty,改成基于Proxy的观察者机制探索。

首先说一下Object.defineProperty的缺点

  • v1①Object.defineProperty没法监控到数组下标的变化,致使直接经过数组的下标给数组设置值,不能实施响应。
  • ②Object.defineProperty只能劫持对象的属性,所以咱们须要对每一个对象的每一个属性进行遍历。Vue2.X里,是经过递归 + 遍历data对象来实现对数据的监控的,若是属性值也是对象那么须要深度遍历,显然若是能劫持一个完整的对象才是更好的选择。

而要取代它的Proxy有如下两个优势:

  • 能够劫持整个对象,并返回一个新对象。
  • 有多种劫持操做(13种)

补充:

  • Proxy是ES6新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操做。Proxy让咱们可以以简洁易懂的方式控制外部对象的访问,其功能很是相似于设计模式中的代理模式。
  • Proxy能够理解为,在目标对象以前设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。
  • 使用Proxy的核心优势是能够交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,须要验证;某些属性的访问控制等)。从而可让对象只须要关注核心逻辑,达到关注点分离,下降对象复杂度等目的。

九、watch、computed和methods的区别

  • methods在从新渲染的时候每次都会被从新的调用;
  • computed 是自动监听依赖值的变化,从而动态返回内容,主要目的是简化模板内的复杂运算。因此区别来源于用法,只是须要动态值,那就用 computed ;须要知道值的改变后执行业务逻辑,才用 watch。
  •  watch也能够影响数据的变化,当绑定的数据方法变化时触发响应的函数,须要在数据变化时执行异步或开销较大的操做时使用watch。

十、virtual-dom原理实现(虚拟dom)

         virtual-dom(简称vdom)的概念大规模的推广仍是得益于react的出现,virtual-dom也是react这个框架的很是重要的特性之一。相比于频繁的手动去操做dom而带来性能问题,vdom很好的将dom作了一层映射关系,进而将在咱们本须要直接进行dom的一系列操做,映射到了操做vdom,而vdom上定义了关于真实dom进行的建立节点,删除节点,添加节点等一系列复杂的dom操做,并将这些操做放到vdom中进行,这样就经过操做vdom来提升直接操做的dom的效率和性能。

       在vue的整个应用生命周期当中,每次须要更新视图的时候便会使用vdom,vdom算法是基于snabbdom算法所作的修改。

实现:

     ①用js对象构造一个虚拟的dom树,插入到文档中;
     ②状态变动时,记录新树和旧树的差别;
     ③把上面的差别构建到真正的dom中。

十二、vue-router

    ❄ 单页面路由跳转的方式:

       ①hash(哈希默认)模式:使用 URL hash 值来做路由。默认模式。
       ②history(mode:history)模式: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
       ③abstract模式(严格模式):支持全部 JavaScript 运行环境,如 Node.js 服务器端。
        根据mode参数来决定采用哪种方式。

        vue-router的实现原理(核心):更新视图但不从新请求页面。

   ❄ vue-router登录权限的判断

         vue-router的登录权限判断主要是在全局钩子函数中进行的,咱们在router.js文件中的定义路由里,将须要登录权限的页面加上meta属性,值是对象的形式,而后在该对象中自定义一个属性,属性值就是一个Boolean值,这时候在main.js文件的全局钩子函数中进行判断,若是须要跳转的页面的自定义属性值为true,那么将进行判断其是否登陆,若是没有登陆,则告诉用户登陆,若是有登陆,那么进行页面跳转。

routes:[
    {
        path:"/home",
        name:"Home",
        components:Home
        meta:{requireAuth:true}
    }
]复制代码

router.beforeEach((to,from,next) => {
    if(to.meta.requireAuth){//判断该路由是否须要登陆权限
        if(store.state.token){//经过vuex的state获取当前的token是否存在
            next()
        }else{
            next({
                path:"/one",
                query:{redirect:to.fullPath}//将跳转的路由path做为参数,登录成功后跳转到该路由
            })
        }
    }else{
        next();
    }    
})复制代码

     ❄ 路由嵌套

routes:[
    {
        path:"/home",
        name:"Home",
        components:Home,
       children:[
            {
                path:"child",
                name:"Child",
                components:"Child"
            }
        ]
    }
]复制代码

1三、Vuex的理解

     定义:Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式储存管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

     使用场景:须要构建一个中大型单页应用,您极可能会考虑如何更好的在组件外部管理状态,Vuex将会成为天然而然的选择。

     优势:当你在state中定义了一个数据以后,能够在所在项目中的任何一个组件里进行获取、进行修改、而且你的修改能够获得全局的响应变动。

     Vuex的运行机制:Vuex提供数据(state)来驱动试图(vue components),经过dispath派发actions,在其中能够作一些异步的操做,而后经过commit来提交mutations,最后mutations来更改state。

     核心:

      ①state:定义初始数据。
      ②mutations:更改Vuex的store中的状态的惟一方法是提交mutation
      ③getters:能够对 state 进行计算操做,它就是 store 的计算属性虽然在组件内也能够作计算属性,可是 getters 能够在多给件之间复用若是一个状态只在一个组件内使用,是能够不用 getters。
      ④actions:异步操做初始数据,其实就是调用mutations里面的方法。
      ⑤module:面对复杂的应用程序,当管理的状态比较多时;咱们须要将vuex的store对象分割成模块(modules)。


     Vuex的映射:

       state(数据)、getters(计算属性)须要映射在computed实例中,而mutations(同步操做放),actions(异步操做方法)等须要放在methods实例中

computed:{
    ...mapState([
        "list",
    ])
}
methods:{
    ...mapMutations([
        "changes",
    ])
}复制代码

1四、描述下vue从初始化页面-->修改数据-->刷新页面UI过程?

       当Vue进入初始化阶段时,一方面 Vue会遍历data中的属性,并用Object.defineProperty将它转化成getter/setterd的形式,实现数据劫持;另外一方面, Vue的指令编译器Compiler对元素节点的各个指令进行解析,初始化视图,并订阅Watcher来更新视图,此时Watcher会将本身 添加到消息订阅器Dep中,此时初始化完毕。
       当数据发生变化时,触发Observer中setter方法, 当即调用Dep.notify( ),Dep这个数组开始遍历全部的订阅者,并 调用其update方法,Vue内部再经过diff算法,patch相应的更新完成对订阅者视图的改变。

1五、Vue的响应式原理

       当一个Vue实例建立时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter而且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每一个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程当中把属性记录为依赖,以后当依赖项的 setter 被调用时,会通知 watcher 从新计算,从而导致它关联的组件得以更新。

1六、Vue.js的特色

  • 简洁:页面由HTML模板+Json数据+Vue实例组成
  • 数据驱动:自动计算属性和追踪依赖的模板表达式
  • 组件化:用可复用、解耦的组件来构造页面
  • 轻量:代码量小,不依赖其余库
  • 快速:精确有效批量DOM更新
  • 模板友好:可经过npm,bower等多种方式安装,很容易融入

1七、插槽的理解

      插槽用于决定将所携带的内容,插入到子组件指定的某个位置,但内容必须在父组件中子组件的标签内定义,在子组件中用<slot></slot>标签接收。slot是组件内部的占位符。

1八、vue-router有哪几种导航钩子

① 全局导航钩子:通常用来判断权限,以及页面丢失时须要执行的操做;
     beforeEach()每次路由进入以前执行的函数。
     afterEach()每次路由进入以后执行的函数。
     beforeResolve()2.5新增
② 单个路由(实例钩子):某个指定路由跳转时须要执行的逻辑。
     beforeEnter()
     beforeLeave()
③ 组件路由钩子:
    beforeRouteEnter()
    beforeRouteLeave()
    beforeRouteUpdate()

1九、vue组件中的data为何是一个函数

       data是一个函数时,每一个组件实例都有本身的做用域,每一个实例相互独立,不会相互影响。Object是引用数据类型,若是不用function返回,每一个组件的data都是内存的同一个地址,一个数据改变了其余也改变了。

20、路由懒加载

        使用缘由:在单页应用中,若是没有应用懒加载,运用webpack打包后的文件将会异常的大,形成进入首页时,须要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则能够将页面进行划分,须要的时候加载页面,能够有效的分担首页所承担的加载压力,减小首页加载用时     

       原理:vue异步组件技术:异步加载,vue-router配置路由 , 使用vue的异步组件技术 , 实现按需加载。

① 第一种:

component:(resolve) => {
    require(["@/components/HelloWorld"],resolve);
}复制代码

② 第二种:

const info = () => import("@/components/info");复制代码

③ 第三种:

const info = (resolve) => {
    import("@/components/info").then(modul => {
        resolve(modul);
    })
}复制代码

④ 第四种:

const info = r => require.ensure([],() => r(
    require("@/components/info")
),"info");复制代码

2一、Vue.js介绍

        Vue.js是一个轻巧、高性能、可组件化的MVVM库,同时拥有很是容易上手的API;Vue.js是一套构建用户界面的 渐进式框架。与其余重量级框架不一样的是,Vue 采用自底向上增量开发的设计。Vue的核心库只关注视图层,而且很是容易学习,很是容易与其它库或已有项目整合。数据驱动+组件化的前端开发。经过尽量简单的 API实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。

2二、scoped原理及穿透方法

           vue中的scoped经过在DOM结构以及css样式上加惟一不重复的标记:data-v-hash的方式,以保证惟一(经过PostCSS转译),达到样式私有模块化的目的。
scoped的3条渲染规则:
① 给HTML的DOM节点加一个不重复的data属性,来表示它的惟一性;
② 在每句css选择器末尾(编译后的生成的css语句)加一个当前组件的data属性选择器来私有化样式;
③ 若是组件内部包含有其余组件,只会给其余组件的最外层标签加上ddan当前组件的data属性。

补充:

      在作项目中,会遇到这么一个问题,即:引用了第三方组件,须要在组件中局部修改第三方组件的样式,而又不想去除scoped属性形成组件之间的样式污染。那么有哪些解决办法呢?
    ①不使用scopeds省略(不推荐);
    ② 在模板中使用两次style标签。
    ③scoped穿透:/deep/ >>>
PostCSS:使用JS插件转换CSS的工具。这些插件能够支持变量和mixins,转换未来的css语法,内联图像等。Autoprefixer是一种很是流行的PostCSS插件。

2三、请说出vue.cli项目中src目录每一个文件夹和文件的用法

assets文件夹是放静态资源;
components是放组件;
router是定义路由相关的配置;
view视图;
app.vue是一个应用主组件;
main.js是入口文件

2四、Vue中key值的做用

       当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。若是数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每一个元素,而且确保它在特定索引下显示已被渲染过的每一个元素。key 的做用主要是为了高效的更新虚拟DOM

2五、Vue怎么重置data

       使用Object.assign(),vm.$data能够获取当前状态下的data,vm.$options.data能够获取到组件初始化状态下的data。

Object.assign(this.$data, this.$options.data())复制代码

2六、组件中写name选项有什么做用

    ①项目使用keep-alive时,可搭配组件的name进行缓存过滤。
    ②DOM作递归组件时须要调用自身name
    ③vue-devtools调试工具里显示的组件名称是由vue中组件name决定的

2七、route和router

route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。

router是“路由实例对象”,包括了路由的跳转方法(pushgo),钩子函数等。

2八、Vue和React的区别

  • 听数据变化的实现原理不一样:Vue 经过 getter/setter 以及一些函数的劫持,能精确快速的计算出 vdom 的差别。这是因为它在渲染过程当中,会跟踪每个组件的依赖关系,不须要从新渲染整个组件树。React 默认是经过比较引用的方式进行的,若是不优化,每当应用的状态被改变时,所有子组件都会从新渲染,可能致使大量没必要要的 VDOM 的从新渲染。
  • 数据流的不一样:Vue 中默认支持双向绑定,组件与 DOM 之间能够经过 v-model 双向绑定。可是,父子组件之间,props 在 2.x 版本是单向数据流。React 一直提倡的是单向数据流。
  • 模板渲染方式的不一样:React 是经过 JSX 渲染模板,而 Vue 是经过一种拓展的 HTML 语法进行渲染

2九、首屏加载优化

①把不常改变的库放到index.html中,经过cdn引入


而后找到 build/webpack.base.conf.js 文件,在 module.exports = { } 中添加如下代码:

externals: {
  'vue': 'Vue',
  'vue-router': 'VueRouter',
  'element-ui': 'ELEMENT',
},复制代码

②vue路由懒加载

③不生成map文件,找到config/index.js文件,修改成productionSourcceMap:false

④vue组件尽可能不要全局引入

⑤使用更轻量级的工具库

⑥开启gzip压缩:这个优化是两方面的,前端将文件打包成.gz文件,而后经过nginx的配置,让浏览器直接解析.gz文件。

⑦首页单独作服务端渲染:若是首页真的有瓶颈,能够考虑用 node 单独作服务端渲染,而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案,由于这样一来增长了学习成本,二来服务端的维护成本也会上升,有时在本机测试没问题,在服务端跑就有问题,为了省心,仍是最大限度的使用静态页面较好。

2九、Vue3.0的了解

        大体有三个点,第一个是关于提出的新API setup()函数,第二个说了对于Typescript的支持,最后说了关于替换Object.defineProperty为 Proxy 的支持。详细说了下关于Proxy代替带来的性能上的提高,由于传统的原型链拦截的方法,没法检测对象及数组的一些更新操做,但使用Proxy又带来了浏览器兼容问题。

30、vue-cli替咱们作了哪些工做

vue-cli是基于 Vue.js 进行快速开发的完整系统,也能够理解成是不少 npm 包的集合。

vue-cli完成的功能:

  • .vue 文件 --> .js 文件
  • ES6 语法 --> ES5 语法
  • Sass,Less,Stylus --> CSS
  • 对 jpg,png,font 等静态资源的处理
  • 热更新
  • 定义环境变量,区分 dev 和 production 模式

若是开发者须要补充或修改默认设置,须要在 package.json 同级下新建一个 vue.config.js 文件

3一、vue的指令

⑴v-bind:给元素绑定属性

⑵v-on:给元素绑定事件

⑶v-html:给元素绑定数据,且该指令能够解析html标签

⑷v-text:给元素绑定数据,不解析标签

⑸v-model:数据双向绑定

⑹v-for:遍历数组

⑺v-if:条件渲染指令,动态在DOM内添加或删除DOM元素

⑻v-else:条件渲染指令,必须跟v-if成对使用

⑼v-else-if:判断多层条件,必须跟v-if成对使用

⑽v-cloak:解决插值闪烁问题

⑾v-once:只渲染元素或组件一次

⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度

⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)

3二、v-for 与 v-if 的优先级

v-for比v-if优先,若是每一次都须要遍历整个数组,将会影响速度,尤为是当之须要渲染很小一部分的时候。

3三、axios的拦截

//响应拦截
axios.interceptors.response.use(function(response){
    //对响应数据作点什么
    return response.data
},function(error){
    //对错误响应作点什么
    return Promise.reject(error)
})复制代码

//请求拦截
axios.interceptors.request.use(function(config){
    //在发送请求以前作些什么
    return config
},function(error){
    //对请求错误作些什么
    return Promise.reject(error)
})复制代码

3四、vue怎么兼容IE

     使用babel-polyfill插件

相关文章
相关标签/搜索