作用域与Vue插槽

本文将回答以下几个问题

1、什么是作用域?

2、为什么会有变量提升?

3、为什么会有闭包?

4、with的作用?下发代码执行结果是什么?

js 复制代码
const foo = { name: 'foo' } 
const bar = { name: 'bar' } 
let sayName = function () {}; 

with(foo) { 
    sayName = function () { console.log(name); }
} 
with(bar) { 
    sayName() 
}

5、Vue作用域插槽为何需要子组件传值?能否直接访问子组件属性?

1、作用域

1.1、何为作用域

存储、查找变量的规则被称为作用域。

作用域共有两种主要的工作模型。

第一种是最为普遍的,被大多数编程语言所采用的词法作用域

另外一种叫作动态作用域

1.2、词法作用域

js采用的是静态的词法作用域,在编译时,会根据各变量的声明位置构建一个嵌套的词法作用域。

这句话是涵盖很多信息的,一些js的机制会因此变得清晰,比如说:

1、变量提升:

为什么会有变量提升?因为对变量声明的处理发生在编译时 ,构建词法作用域的阶段,而变量的使用是发生在运行时。

所以最后一行的变量声明的处理,也比第一行的变量使用的处理要早,自然而然就有变量提升了。

那么let、const呢?也有提升么?

js引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(遇到 var 声明) ,要么将声明放TDZ(临时死区)中(遇到let、const 声明)。

访问TDZ 中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会TDZ中移出,然后方可正常访问。

2、闭包:

既然编译阶段就会生成词法作用域,那么函数的作用域就只会取决与他声明的位置,而与他执行的位置无关。

不管函数的引用最终被传递到哪里,在什么时机执行,他都保有在其声明位置的作用域。

1.3、动态作用域

动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。

如果js使用的是动态作用域,那他的执行效果就会像下面这样:

js 复制代码
function foo() { 
    console.log( a ); // 3(不是 2 !) 
} 
function bar() { 
    var a = 3; 
    foo(); 
} 
var a = 2; 
bar();

这个其实就有点类似于js中的this值的机制,其值取决去运行时的上下文。

1.4、块作用域

函数是常见的作用域块,此外with、try/catch等也会创建一个块作用域,我们重点关注下if与for。

我们来看一下一下代码会输出什么结果

js 复制代码
if (1) { 
    var a = 123 
} 
console.log(a) 
if (1) { 
    let a2 = 123 
} 
console.log(a2)

答案是

let、const关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。

1.5、改变作用域的方法,with的作用

'with'语句将某个对象添加到作用域链的顶部

用 with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。

2、Vue插槽

2.1、普通插槽

1、示例代码

js 复制代码
<template>
  <div class="test">
    <SlotTest>
      <div>
        <p>i m slot</p>
      </div>
    </SlotTest>
    <div>
      <p>i m test</p>
    </div>
  </div>
</template>

<script>
const SlotTest = {
  template: `
    <div>
      i m SlotText
      <div>
        <slot></slot>
      </div>
    </div>
  `
}
export default {
  name: 'test',
  components: { SlotTest },
}
</script>

2、当一个vnode是组件的vnode时,其在模板中编写的子节点vnode并不像普通节点一样放在children属性中,而是存放在componentOptions对象的某个属性下

3、然后子组件中,可以通过标签,来指定父组件传入节点的渲染位置

js 复制代码
const SlotTest = {
  template: `
    <div>
      i m SlotText
      <div>
        <slot></slot>
      </div>
    </div>
  `,
};

这个是如何实现的呢?

4、Vue不管是我们平常写的template标签、还是template字符串模板;vue都会转成render函数去执行,而对于 标签,vue则会将其转成._t函数。

(注意一下,这里生成的render函数的函数体是用with(this)包裹的,这是我们在写vue模板的时候可以直接访问vue实例的属性而无需this.的原因;但后面会看到另一个有意思的场景)

而这个函数本身,就会去取上面那个在父组件渲染过程中被放在子组件占位VNode的componentOptions.children 里的vnode返回。

所以,对于普通插槽而言,插槽中的节点的vnode是在父组件的render过程中被生成,在子组件render的过程被返回添加到子组件的vnode树中,并在子组件patch的过程被挂载的。

2.2、作用域插槽

1、示例代码

js 复制代码
<template>
  <div class="test">
    <SlotTest v-slot:default="{ msg }">
      <div>
        <p>i m slot {{ msg }}</p>
      </div>
    </SlotTest>
    <div>
      <p>i m test</p>
    </div>
  </div>
</template>

<script>
const SlotTest = {
  template: `
    <div>
      i m SlotText
      <div>
        <slot :msg="msg"></slot>
      </div>
    </div>
  `,
  data() {
    return {
      msg: 'haha'
    }
  }
};
export default {
  name: 'TestComp',
  components: { SlotTest },
};
</script>

2、对于作用域插槽,其vnode并不会在父组件的render中生成,而是变成一个函数(该函数会返回这些vnode),存放在子组件的占位vnode的属性中。

父组件生成的render函数

父组件执行完render后返回的vnode

可以看到原先普通插槽存放插槽vnode的componentOptions.children已经是一个undefined,

取而代之的是放在data.scopedSlots中的一个函数。

3、然后在子组件中,由标签转换来的_t方法就会去执行这个函数,并将标签的props属性作为参数值传入。

但这里有一个地方需要注意就是,vue组件的生成而来的render函数都会使用with(this)将其包裹。

那么对于作用域插槽的生成方法,就是在父组件实例的with中生成,在子组件实例的with中执行。

js 复制代码
with($parentVm) { 
    scopedSlots.default = function () {...} 
} 
with($childVm) { 
    scopedSlots.default(params) 
}

通过回顾前面的内容我们知道,函数的作用域是取决于其声明的位置,而不是其执行的位置,那么我们的问题4、5就有答案了

相关推荐
Dread_lxy15 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜2 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js