JavaScript根据属性获取其父对象(父属性)-获取属性的父级

thbcm阅读(169)

众所周知,element 对象可以使用 parentNode 属性来访问其父元素。
有些时候我们会希望 JavaScript 对象可以访问到某一个属性的父对象。
我们可以参考 document 的做法——

 在创建对象时自动生成 parentNode 属性指向其父级

使用 ES6 提供的 Proxy() 来代理对象赋值 Proxy()的官方文档

话不多说上代码

function mObj(Obj) {
	var obj = new Proxy(Obj, {
		set: function (obj, pro, val, Pro) {
			if (typeof val == 'object') {
				val.parent = Pro
				val = mObj(val)
			}
			obj[pro] = val
			return val
		}
	})
	return obj
}

我们只需要用 mObj 方法来初始化我们需要代理的对象

var obj = mObj({})

向其添加属性

注!只能添加空对象,否则其属性将失去代理

obj.a = {}
obj.b = {}
obj.a.aa = {}
obj.b.bb = {}
console.log(obj) // Proxy {a: Proxy, b: Proxy}

Proxy就是代理对象

尝试使用obj.parent获取它的父级

console.log(obj.a.parent,obj.b.parent) 
// Proxy {a: Proxy, b: Proxy} Proxy {a: Proxy, b: Proxy}
console.log(obj.a.aa.parent,obj.b.bb.parent)  
// Proxy {parent: Proxy, aa: Proxy} Proxy {parent: Proxy, bb: Proxy}

可以看到已经成功获取了属性的父级那么它可以用来干什么呢?

1. 定位父级

obj.a.parent // Proxy {a: Proxy, b: Proxy}

2. 生成兄弟属性

var p = obj.a
p.parent.c = {}
obj.c // Proxy {parent: Proxy}

3. 检测是否为兄弟属性

var p1 = obj.a.parent, p2 = obj.b.parent
var p3 = obj.a.aa.parent, p4 = obj.b.bb.parent
p1 == p2 // true
p3 == p4 // false

4. 定位最高父级

p5 = obj.a.aa
while(p5.parent) {
	p5 = p5.parent
}
p5 // Proxy {a: Proxy, b: Proxy}
p5 == obj // true

判断JS数据类型的四种方法

thbcm阅读(206)

在 ECMAScript 规范中,共定义了 7 种数据类型,分为 基本类型 和 引用类型 两大类,如下所示:

基本类型:String、Number、Boolean、Symbol、Undefined、Null 引用类型:Object

基本类型也称为简单类型,由于其占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈中,即按值访问。

引用类型也称为复杂类型,由于其值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此,其值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问。引用类型除 Object 外,还包括 Function 、Array、RegExp、Date 等等。

鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型。对于这个问题,JavaScript 也提供了多种方法,但遗憾的是,不同的方法得到的结果参差不齐。

下面介绍常用的4种方法,并对各个方法存在的问题进行简单的分析。

1、typeof

typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。

typeof”;// string 有效

typeof1;// number 有效

typeofSymbol();// symbol 有效

typeoftrue;//boolean 有效

typeofundefined;//undefined 有效

typeofnull;//object 无效

typeof[] ;//object 无效

typeofnewFunction();// function 有效

typeofnewDate();//object 无效

typeofnewRegExp();//object 无效

有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值:

  • 对于基本类型,除 null 以外,均可以返回正确的结果。
  • 对于引用类型,除 function 以外,一律返回 object 类型。
  • 对于 null ,返回 object 类型。
  • 对于 function 返回 function 类型。

其中,null 有属于自己的数据类型 Null , 引用类型中的 数组、日期、正则 也都有属于自己的具体类型,而 typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我们想要的结果。

2、instanceof

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型,我们用一段伪代码来模拟其内部执行过程:

instanceof (A,B) = {

varL = A.__proto__;

varR = B.prototype;

if(L === R) {

// A的内部属性 __proto__ 指向 B 的原型对象

returntrue;

}

returnfalse;

}

从上述过程可以看出,当 A 的 proto 指向 B 的 prototype 时,就认为 A 就是 B 的实例,我们再来看几个例子:

[] instanceof Array;// true

{} instanceof Object;// true

newDate() instanceof Date;// true

function Person(){};

newPerson() instanceof Person;

[] instanceof Object;// true

newDate() instanceof Object;// true

newPerson instanceof Object;// true

我们发现,虽然 instanceof 能够判断出 [ ] 是Array的实例,但它认为 [ ] 也是Object的实例,为什么呢?

我们来分析一下 [ ]、Array、Object 三者之间的关系:

从 instanceof 能够判断出 [ ].proto  指向 Array.prototype,而 Array.prototype.proto 又指向了Object.prototype,最终 Object.prototype.proto 指向了null,标志着原型链的结束。因此,[]、Array、Object 就在内部形成了一条原型链:

从原型链可以看出,[] 的 proto  直接指向Array.prototype,间接指向 Object.prototype,所以按照 instanceof 的判断规则,[] 就是Object的实例。依次类推,类似的 new Date()、new Person() 也会形成一条对应的原型链 。因此,instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

variframe = document.createElement(‘iframe’);

document.body.appendChild(iframe);

xArray = window.frames[0].Array;

vararr =newxArray(1,2,3);// [1,2,3]

arr instanceof Array;// false

if(Array.isArray(value)){

//对数组执行某些操作

}

Array.isArray() 本质上检测的是对象的 [[Class]] 值,[[Class]] 是对象的一个内部属性,里面包含了对象的类型信息,其格式为 [object Xxx] ,Xxx 就是对应的具体类型 。对于数组而言,[[Class]] 的值就是 [object Array] 。

3、constructor

当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。如下所示:

当执行 var f = new F() 时,F 被当成了构造函数,f 是F的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor == F

可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。

同样,JavaScript 中的内置对象在内部构建时也是这样做的:

细节问题:

null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。2. 函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object

为什么变成了 Object?

因为 prototype 被重新赋值的是一个 { }, { } 是 new Object() 的字面量,因此 new Object() 会将 Object 原型上的 constructor 传递给 { },也就是 Object 本身。

因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。

4、toString

toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

对于 Object 对象,直接调用 toString()  就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

Object.prototype.toString.call(”) ;  // [object String]

Object.prototype.toString.call(1) ;   // [object Number]

Object.prototype.toString.call(true) ;// [object Boolean]

Object.prototype.toString.call(Symbol());//[object Symbol]

Object.prototype.toString.call(undefined) ;// [object Undefined]

Object.prototype.toString.call(null) ;// [object Null]

Object.prototype.toString.call(newFunction()) ;// [object Function]

Object.prototype.toString.call(newDate()) ;// [object Date]

Object.prototype.toString.call([]) ;// [object Array]

Object.prototype.toString.call(newRegExp()) ;// [object RegExp]

Object.prototype.toString.call(newError()) ;// [object Error]

Object.prototype.toString.call(document) ;// [object HTMLDocument]

Object.prototype.toString.call(window) ;//[object global] window 是全局对象 global 的引用

Vue框架的基本使用

thbcm阅读(156)

Vue 的使用步骤

  • 1.创建一个标签,用于数据的填充
  • 2.引入 Vue.js 库文件
  • 3.实例化 Vue 对象
  • 4.把 Vue 提供的数据填充到标签里

代码示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <div>{{ msg }}</div>
        <div>{{ 1 + 1}}</div>
        <div>{{"===" + msg + "==="}}</div>
    </div>
    <script src="js/vue-2.5.16.js"></script>
    <script type='text/javascript'>
        var vm = new Vue({
            el: '#app', // 元素的挂载位置(值可以是CSS选择器或者DOM元素)
            // data 模型数据(值是一个对象)
            data: {
                msg: 'Hello Vue',
            }
        });
    </script>
</body>

</html>

实例参数分析

  • 在 Vue 对象里 el 为元素的挂载位置(值可以是 CSS 选择器也可以是 DOM 元素) data 为模型数据(类型是一个对象)

插值表达式的用法

  • 使用插值表达式{{}}将数据填充到 HTML 标签里
  • 插值表达式还支持 JavaScript 的基本计算操作

Vue代码运行原理分析

概述编译过程概念(Vue 语法→原生 js 语法)

Vue前端交互模式、Promise用法(回调地狱)

thbcm阅读(161)

Promise 概述

Promise 是异步编程的一种解决方案,从语法上讲,Promise 是一个对象,从它可以获取异步操作的消息。

优点:

  • 可以避免多层异步调用嵌套问题(回调地狱)
  • Promise 对象提供了简洁的 API,使得控制异步操作更加容易

Promise基本用法

  • 实例化 Promise 对象,构造函数中传递函数,该函数中用于处理异步任务
  • resolve 和 reject 两个参数用于处理成功和失败两种情况,并通过 p.then 获取处理结果
var p = new Promise(function(resolve,reject){
    //实现异步任务...
    //成功时调用
    resolve();
    //失败时调用
    reject();
});
p.then(function(ret){
    //从resolve得到正常结果
},function(ret){
    //从reject得到错误信息
});  

基于 Promise 处理 Ajax 请求

  1. 处理原生 Ajax
function queryData(url){
    return new Promise(function(resolve,reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if(xhr.readyState!=4) return;
            if(xhr.status == 200){
                resolve(xhr.responseText);
            }else{
                reject('出错了');
            }
        }
        xhr.open('get',url);
        xhr.send(null);
    });
}
//调用
queryData('http://localhost:3000/data').then(
    function (data) {
        console.log(data);
    },
    function (data) {
        console.log(data);
    }
)
  1. 发送多次 ajax 请求(解决回调地狱)
queryData('http://localhost:3000/data')
    .then(function (data) {
        console.log(data);
        //异常情况可以不处理
        return queryData('http://localhost:3000/data1');
    })
    .then(function (data1) {
        console.log(data1);
        return queryData('http://localhost:3000/data2');
    })
    .then(function (data2) {
        console.log(data2);
    });

then参数中的函数返回值

  1. 返回 Promise 实例对象返回的该实例对象会调用下一个 then
  2. 返回普通值返回的普通值会直接传递给下一个 then,通过 then 参数中函数的参数接收该值

Promise常用的API

1.实例方法

* p.then()得到异步任务的正确结果
* p.catch()获取异常信息
* p.finally()成功与否都会执行(暂时还不是正式标准)
foo()
    .then(function (data) {
        console.log(data);
    })
    .catch(function (data) {
        console.log(data);
    })
    .finally(function () {
        console.log('finish');
    })

也可以写为:

foo()
    .then(function (data) {
        console.log(data);
    },
    function (data) {
        console.log(data);
    })
    .finally(function () {
        console.log('finish');
    })  

2.对象方法

  • Promise.all() 并发处理多个异步任务,所有任务都执行完成才能得到结果
//p1,p2,p3为Promise实例对象任务
Promise.all([p1,p2,p3]).then((result)=>{
    console.log(result);
})
  • Promise.race()并发处理多个异步任务,只要有一个任务完成就能得到结果
Promise.race([p1,p2,p3]).then((result)=>{
    console.log(result);
})

Vue 组件生命周期

thbcm阅读(193)

1. 组件的生命周期的四个阶段

组件的生命周期分为四个阶段:
- create(创建)
- mount(挂载)
- update(更新)
- destroy(销毁)

2. 生命周期钩子函数

2.1 钩子函数定义

  • 先看看官方的解释:    每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
  • 钩子函数到底是个啥意思呢?    组件在加载的过程中,加载到某个阶段时,自动触发的函数

2.2 钩子函数理解与实例

钩子函数有哪些?怎么用?

结合图来看~~~下图展示了组件的生命周期的过程:(下面会详细解释每个)

1. beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用;如果在 beforeCreate 函数里面去访问 data 数据对象,是访问不到的

<body>
  <div id="app">
    <input type="text" v-model="msg">
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{
        msg:'123'
      },
      watch:{
        msg:function(){
          console.log("我变了");
        }
      },
      beforeCreate(){
        console.log("创建组件data对象之前自动触发~~~");
        console.log("beforeCreate",this.msg);
      }
    })
  </script>
</body>

结果:在 beforeCreate 函数中无法得到 msg 的值

2. created

  • 在执行 created 函数前,已经有了数据对象 data,以及完成了事件的初始化,但$el 属性目前不可见;
  • 在 created 函数里面可以使用数据,也可以更改数据;
  • 调用 Vue 方法,可以获取原本 HTML 上的直接加载出来的 DOM,但是无法获取到通过挂载模板生成的 DOM(例如:v-for循环遍历 Vue.list 生成 li)
<body>
  <div id="app">
    <input type="text" v-model="msg">
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{
        msg:'123'
      },
      watch:{
        msg:function(){
          console.log("我变了");
        }
      },
      beforeCreate(){
        console.log("创建组件data对象之前自动触发~~~");
        console.log("beforeCreated",this.msg);

      },
      created:function(){
        console.log("创建组件data对象之后自动触发~~~");
        this.msg=100000;
        console.log("created",this.msg);   
        console.log("li数量:",document.getElementsByTagName("li").length);
        console.log("p数量:",document.getElementsByTagName("p").length); 
      }
    })
  </script>

结果:在 created 函数中我们成功的拿到了、更改了 msg 的值。并且数据被更改后出发了watch 属性中的方法。 而且可以获取原本HTML 上的直接加载出来的 DOM(p 元素),但是无法获取到通过挂载模板生成的 DOM(例如:v-for 循环遍历 Vue.list 生成 li 元素)

注意:create阶段还没有创建虚拟dom

3.beforeMount

  • 在挂载开始之前被调用,相关的(渲染函数)模板首次被调用。$el 属性可见。但此时在 beforeMount 函数中依然无法无法获取到通过挂载模板生成的DOM(例如:v-for 循环遍历 Vue.list 生成 li 元素)
  • 在此函数之前都没有建立虚拟dom
beforeMount:function(){
        console.log(this.$el);
        console.log("li数量:",document.getElementsByTagName("li").length);
      }

结果:

4.mounted

  • 执行完 beforeMount 函数后,$e l elel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子函数。
  • 这里才能获取初始数据list渲染出来的li
  • 挂载后我们已经可以看见网页内容了,只不过还未操作
mounted:function(){
        console.log(this.$el);
        console.log("li数量:",document.getElementsByTagName("li").length);
      }

结果:同样的代码,放在 beforeMount 和 mounted 函数中结果完全不一样

在 update 阶段,虚拟 dom 监听数据变化,随时更新 dom

5. beforeUpdate 当数据发生变化的时候,beforeUpdate 这个钩子函数才会执行 

6. updated 虚拟 dom 重新渲染后执行

7.beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用 

8.destroyedVue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

<body>
  <div id="app">
    <input type="text" v-model="msg">
    <ul>
      <li v-for="(elem,index) of list" :key="index">{{elem}}</li>
    </ul>
    <p>p1</p>
    <p>p2</p>
    <p>p3</p>
    <h1>数量:{{count}}</h1>
  </div>
  <script>
    new Vue({
      el:"#app",
      data:{
        msg:'123',
        list:['a','b','c'],
        count:0
      },
      watch:{
        msg:function(){
          console.log("我变了");
        }
      },
      beforeCreate(){
        console.log("创建组件data对象之前自动触发~~~");
        console.log("beforeCreated",this.msg);

      },
      created:function(){
        console.log("创建组件data对象之后自动触发~~~");
        this.msg=100000;
        console.log("created",this.msg); 
        console.log("li数量:",document.getElementsByTagName("li").length);
        console.log("p数量:",document.getElementsByTagName("p").length);  
      },
      beforeMount:function(){
        console.log(this.$el);
        console.log("li数量:",document.getElementsByTagName("li").length);
      },
      mounted:function(){
        console.log(this.$el);
        console.log("li数量:",document.getElementsByTagName("li").length);
        setInterval(()=>{
          this.count++;
        },1000)
      },
      beforeUpdate(){
        console.log("更新组件的data变量前自动触发~~~")
      },
      updated(){
        console.log("更新组件的data变量后自动触发~~~");
        if(this.count>3){
          this.$destroy();
        }
      },
      beforeDestroy(){
        console.log("销毁当前组件前自动触发~~~")
      },
      destroyed(){
        console.log("销毁组件后自动触发~~~");
      }
    })
  </script>
</body>

结果:

组件的生命周期就是这样啦~

AngularJS中的:host 、:host-context、::ng-deep详解

thbcm阅读(186)

:host 与 ::ng-deep

 :host 表示选择当前的组件。

::ng-deep 可以忽略中间 className 的嵌套层级关系。直接找到你要修改的 className。

在使用一些第三方的组件的时候,要修改组件的样式。
这种情况下使用:

:host ::ng-deep .className{
    新的样式......
}
:host {
     background: #F1F1F1;
     overflow: hidden;
     padding: 24px;
     display: block;
}
 
.card-container ::ng-deep .ant-tabs-card .ant-tabs-content {
     height: 120px;
     margin-top: -16px;
}

不过官方文档上说,ng-deep 在未来的版本中将被放弃,不知道未来会变成什么样的语法。使用的时候,记得为未来 Angular 升级带来的变化做准备。

:host-context

::ng-deep 当你没有编写组件并且无法访问其源代码时,通常需要这样做,但是 :host-context 当你这样做时,它可能是一个非常有用的选项.

例如,我<h1>在我设计的组件中有一个黑色标题,我希望能够在黑暗的主题背景上显示时将其更改为白色.

如果我无法访问源代码,我可能必须在父代的 css 中执行此操作:

.theme-dark widget-box ::ng-deep h1 { color: white; }

但相反,:host-context您可以在组件执行此操作.SCSS代码

h1 
 {
     color: black;       // default color
     :host-context(.theme-dark) &
     {
         color: white;   // color for dark-theme
     }
     // OR set an attribute 'outside' with [attr.theme]="'dark'"
     :host-context([theme='dark']) &
     {
         color: white;   // color for dark-theme
     }
 }

这将查看组件链中的任何位置,.theme-dark 如果找到,则将 css 应用于 h1.这是一个很好的选择,过分依赖 ::ng-deep,而往往是必要的反模式.

在这种情况下,它&被替换为h1(这就是sass/scss的工作方式),因此您可以将”正常”和主题/替代 css 紧挨着定义,这非常方便.

官网资料

特殊的选择器

组件样式中有一些从影子(Shadow) DOM 样式范围领域引入的特殊选择器:

:host

使用 :host 伪类选择器,用来选择组件宿主元素中的元素(相对于组件模板内部的元素)。

src/app/hero-details.component.css

:host {
  display: block;
  border: 1px solid black;
}

:host 选择是是把宿主元素作为目标的唯一方式。除此之外,你将没办法指定它, 因为宿主不是组件自身模板的一部分,而是父组件模板的一部分。

要把宿主样式作为条件,就要像函数一样把其它选择器放在 :host 后面的括号中。

下一个例子再次把宿主元素作为目标,但是只有当它同时带有 active CSS 类的时候才会生效。

src/app/hero-details.component.css

:host(.active) {
  border-width: 3px;
}

:host-context

有时候,基于某些来自组件视图外部的条件应用样式是很有用的。 例如,在文档的 <body> 元素上可能有一个用于表示样式主题 (theme) 的 CSS 类,你应当基于它来决定组件的样式。

这时可以使用 :host-context() 伪类选择器。它也以类似 :host() 形式使用。它在当前组件宿主元素的祖先节点中查找 CSS 类, 直到文档的根节点为止。在与其它选择器组合使用时,它非常有用。

在下面的例子中,只有当某个祖先元素有 CSS 类 theme-light 时,才会把 background-color 样式应用到组件内部的所有 <h2> 元素中。

src/app/hero-details.component.css

:host-context(.theme-light) h2 {
  background-color: #eef;
}

已废弃 /deep/、>>> 和 ::ng-deep

组件样式通常只会作用于组件自身的 HTML 上。

把伪类 ::ng-deep 应用到任何一条 CSS 规则上就会完全禁止对那条规则的视图包装。任何带有 ::ng-deep 的样式都会变成全局样式。为了把指定的样式限定在当前组件及其下级组件中,请确保在 ::ng-deep 之前带上 :host 选择器。如果 ::ng-deep 组合器在 :host 伪类之外使用,该样式就会污染其它组件。

这个例子以所有的 <h3> 元素为目标,从宿主元素到当前元素再到 DOM 中的所有子元素:

src/app/hero-details.component.css

:host ::ng-deep h3 {
  font-style: italic;
}

/deep/ 组合器还有两个别名:>>> 和 ::ng-deep。

/deep/ 和 >>> 选择器只能被用在仿真 (emulated) 模式下。 这种方式是默认值,也是用得最多的方式。 

CSS 标准中用于 “刺穿 Shadow DOM” 的组合器已经被废弃,并将这个特性从主流浏览器和工具中移除。 因此,我们也将在 Angular 中移除对它们的支持(包括 /deep/、>>> 和 ::ng-deep)。 目前,建议先统一使用 ::ng-deep,以便兼容将来的工具。

推荐好课:小白学前端:Angular入门Angular教程

深入理解vue中的slot与slot-scope

thbcm阅读(219)

写在前面

vue 中关于插槽的文档说明很短,语言又写的很凝练,再加上其和 ​methods​,​data​,​computed​ 等常用选项使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”,于是就关闭了 vue 说明文档。

实际上,插槽的概念很简单,下面通过分三部分来讲。这个部分也是按照 vue 说明文档的顺序来写的。

进入三部分之前,先让还没接触过插槽的同学对什么是插槽有一个简单的概念:插槽,也就是 slot,是组件的一块 HTML 模板,这块模板显示不显示、以及怎样显示由父组件来决定。 实际上,一个 slot 最核心的两个问题这里就点出来了,是显示不显示和怎样显示。

由于插槽是一块模板,所以,对于任何一个组件,从模板种类的角度来分,其实都可以分为非插槽模板和插槽模板两大类。非插槽模板指的是 html 模板,指的是‘​div​、​span​、​ul​、​table​’这些,非插槽模板的显示与隐藏以及怎样显示由插件自身控制;插槽模板是 slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的 html 模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot 写在组件 template 的哪块,父组件传过来的模板将来就显示在哪块。

单个插槽 | 默认插槽 | 匿名插槽

首先是单个插槽,单个插槽是 vue 的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置 name 属性。

单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name 属性)不同就可以了。

下面通过一个例子来展示。

父组件:

<template>
    <div class="father">
        <h3>这里是父组件</h3>
        <child>
            <div class="tmpl">
              <span>菜单1</span>
              <span>菜单2</span>
              <span>菜单3</span>
              <span>菜单4</span>
              <span>菜单5</span>
              <span>菜单6</span>
            </div>
        </child>
    </div>
</template>

子组件:

<template>
    <div class="child">
        <h3>这里是子组件</h3>
        <slot></slot>
    </div>
</template>

在这个例子里,因为父组件在<child></child>里面写了 html 模板,那么子组件的匿名插槽这块模板就是下面这样。也就是说,子组件的匿名插槽被使用了,是被下面这块模板使用了。

<div class="tmpl">
  <span>菜单1</span>
  <span>菜单2</span>
  <span>菜单3</span>
  <span>菜单4</span>
  <span>菜单5</span>
  <span>菜单6</span>
</div>

最终的渲染结果如图所示:

注:所有 demo 都加了样式,以方便观察。其中,父组件以灰色背景填充,子组件都以浅蓝色填充。

具名插槽

匿名插槽没有 name 属性,所以是匿名插槽,那么,插槽加了 name 属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次。出现在不同的位置。下面的例子,就是一个有两个具名插槽和单个插槽的组件,这三个插槽被父组件用同一套 css 样式显示了出来,不同的是内容上略有区别。

父组件:

<template>
  <div class="father">
    <h3>这里是父组件</h3>
    <child>
      <div class="tmpl" slot="up">
        <span>菜单1</span>
        <span>菜单2</span>
        <span>菜单3</span>
        <span>菜单4</span>
        <span>菜单5</span>
        <span>菜单6</span>
      </div>
      <div class="tmpl" slot="down">
        <span>菜单-1</span>
        <span>菜单-2</span>
        <span>菜单-3</span>
        <span>菜单-4</span>
        <span>菜单-5</span>
        <span>菜单-6</span>
      </div>
      <div class="tmpl">
        <span>菜单->1</span>
        <span>菜单->2</span>
        <span>菜单->3</span>
        <span>菜单->4</span>
        <span>菜单->5</span>
        <span>菜单->6</span>
      </div>
    </child>
  </div>
</template>

子组件:

<template>
  <div class="child">
    // 具名插槽
    <slot name="up"></slot>
    <h3>这里是子组件</h3>
    // 具名插槽
    <slot name="down"></slot>
    // 匿名插槽
    <slot></slot>
  </div>
</template>

显示结果如图:

可以看到,父组件通过 html 模板上的 slot 属性关联具名插槽。没有 slot 属性的 html 模板默认关联匿名插槽。

作用域插槽 | 带数据的插槽

最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的 template 里面写

匿名插槽
<slot></slot>
具名插槽
<slot name="up"></slot>

但是作用域插槽要求,在 slot 上面绑定数据。也就是你得写成大概下面这个样子。

<slot name="up" :data="data"></slot>
 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    },
}

我们前面说了,插槽最后显示不显示是看父组件有没有在 child 下面写模板,像下面那样。

<child>
   html模板
</child>

写了,插槽就总得在浏览器上显示点东西,东西就是 html 该有的模样,没写,插槽就是空壳子,啥都没有。OK,我们说有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的 html+css 共同决定,但是这套样式里面的内容呢?

正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。

我们再来对比,作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式由包括内容的,上面的例子中,你看到的文字,“菜单1”,“菜单2”都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。

下面的例子,你就能看到,父组件提供了三种样式(分别是 flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个人名数组。

父组件:

<template>
  <div class="father">
    <h3>这里是父组件</h3>
    <!--第一次使用:用flex展示数据-->
    <child>
      <template slot-scope="user">
        <div class="tmpl">
          <span v-for="item in user.data">{{item}}</span>
        </div>
      </template>
 
    </child>
 
    <!--第二次使用:用列表展示数据-->
    <child>
      <template slot-scope="user">
        <ul>
          <li v-for="item in user.data">{{item}}</li>
        </ul>
      </template>
 
    </child>
 
    <!--第三次使用:直接显示数据-->
    <child>
      <template slot-scope="user">
       {{user.data}}
      </template>
 
    </child>
 
    <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
    <child>
      我就是模板
    </child>
  </div>
</template>

子组件:

<template>
  <div class="child">
 
    <h3>这里是子组件</h3>
    // 作用域插槽
    <slot  :data="data"></slot>
  </div>
</template>
 
 export default {
    data: function(){
      return {
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    }
}

结果如图所示:

推荐好课:Vue项目实战精讲Vue2.x微课

通过 vue 实现轮播图效果源码

thbcm阅读(166)

仅供初学者学习 vue 思想

1.原理:

  • v-on:click="prev"​ v-on 用来绑定事件
  • v-if="条件"​ v-if 来控制 a 这个 dom 是否存在(显示),性能消耗较大;
  • v-show="条件" ​v-show 来控制元素的 display 属性
  • v-bind:src="#"

通过 vue 实现轮播图与 js 实现相比较

vue 只是通过 ​v-show="index!=0"​ 和 ​v-show="index<imgArr.length-1"​  来控制左右箭头的显示隐藏,简化了 js 对 DOM 的繁琐操作;

2.代码(css样式代码可自行设计)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>轮播图</title>
		<script src="jsue.js" type="text/javascript" charset="utf-8"></script>
		<link rel="stylesheet" type="texts" href=".s/lunbotu.css"/>
	</head>
	<body>
		<div id="mask">
			<!-- 轮播图片 -->
			<img :src="imgArr[index]"  id="maskimg">		
			<!-- 左箭头 -->
			<a href="javascript:void(0)" class="aBlock" @click="prev" v-if="index!=0">
				<img src="./img1/prev.png" class="left" >
			</a>
			<!-- 右箭头 -->
			<a href="javascript:void(0)" class="aBlock" id="ARight" @click="next" v-if="index<imgArr.length-1">
				<img src="./img1/next.png" class="right" >
			</a>
		</div>
		<script>
			var vmm =new Vue({
				el:"#mask",
				data:{
					 imgArr:[
						 "./img/01.png",
						 "./img/00.png",
						 "./img/02.png",
						 "./img/03.png",
						 "./img/04.png",
						 "./img/05.png",
						 "./img/06.png",
						 "./img/07.png",
					 ],
					 index:0
				},
				methods:{
					 prev:function(){
						this.index--
					 },
					next:function(){
						 this.index++
					}
				}
			})
		</script>
	</body>
<html>

JS中parseInt()和Number()区别

thbcm阅读(173)

学习目标:

parseInt()、Number()这两个函数用到最多的地方就是把一个字符串转换成数据类型,那么他们都有哪些区别?

学习内容:

parseInt()函数将给定的字符串以指定的基数解析为整数。parseInt(string,radix)第二个参数表示使用的进制,我们一般使用10进制,也可能会有到8或者16进制。为了避免对“0”和“0x”开头的字符串解析错误,各种 javascript 编程规范都规定必须要明确给出第二个参数的值,如 parseInt(“123”,10).

parseInt('16', 8)  = 14
parseInt('10', 8)  = 8

parseInt('16', 10)  = 16
parseInt('10', 10)  = 10

parseInt('16', 16)  = 22
parseInt('10', 16)  = 16

parseInt 从头解析 string 为整数,在遇到不能解析的字符时就返回已经解析的整数部分,如果第一个字符就不能解析,就直接返回NaN。

Number() 在不用 new 操作符时,可以用来执行类型转换。如果无法转换为数字,就返回 NaN。 像“123a”,parseInt() 返回是123,Number() 返回是 NaN,不同类型的字符串使用这两个函数的转换区别:

// 当字符串是由数字组成的时候 他们转换的数字一样的没有差别  
let numStr = '123'
console.log(parseInt(numStr))   //123
console.log(Number(numStr))		//123

// 当字符串是由字母组成的时候 
let numStr = 'abc'
console.log(parseInt(numStr))   //NaN
console.log(Number(numStr))		//NaN

// 当字符串是由数字和字母组成的时候 
let numStr = '123a'
console.log(parseInt(numStr))   //123
console.log(Number(numStr))		//NaN

// 当字符串是由0和数字
let numStr = '0123'
console.log(parseInt(numStr))   //123
console.log(Number(numStr))		//123

// **当字符串包含小数点**
let numStr = '123.456'
console.log(parseInt(numStr))		//123
console.log(Number(numStr))			//123.456

// **当字符串为null时**
let numStr = null
console.log(parseInt(numStr))		//NaN
console.log(Number(numStr))			//0

// **当字符串为''(空)时**
let numStr = ''
console.log(parseInt(numStr))		//NaN
console.log(Number(numStr))			//0

学习总结:

1、当字符串是由数字组成的时候 他们转换的数字一样的没有差别;如果字符串不含数字全是字母,这两个方法也都只是返回 NaN 结果;当字符串是由0和数字组成时,都是解析除0外的全部数字;
2、 当字符串是由数字和字母组成的时候 ①字母在开头,这两个方法也都只是返回 NaN 结果②字母不在开头 Number 方法返回NaN,pareseInt 方法返回字母之前的数据
3 、parseInt 对于非 String 类型的值要先转换为 String 类型再操作

for循序为何要用let?

thbcm阅读(179)

在 ES5 当中使用 for 循环都是采用 var,而在 ES6 中都是采用的 let,并且我们更推荐于 let,这是为何?
因为 var 是在 js 语言设计者 Brendan Eich 设计的一种缺陷,这种缺陷不能更改,并且作者在十年前就提出了修复此缺陷.
在进行讲解的时候,小伙伴们需要知道在 js 语言中,if 和 for 是没有作用域的,只有 function 才拥有,var 是全局变量,let 是块级作用域,const 是常量(不可改变的变量称为常量);可以把 let 看做成更完美的 var

//ES5中使用for循环点击事件 //bug:每次点击都是最后一个点击按钮触发点击
for(var i=0;i<btns.length;i++){
btns[i].addEventListener('click',function(i){
console.log(`第${i}按钮发生点击`);
})
}
//es5中for循环使用var定义的,就相当于定义了一个全局的i,每次循环i++都可以进行改变i的值,
//var进行变量提升和代码预解析后var是在全局最前面的,es5的解决方案是采用闭包进行解决;
//解决方案
for(var i=0;i<btns.length;i++){

( function(num){
btns[i].addEventListener('click',function(i){
console.log(`第${num}按钮发生点击`);
})
})(i);
}
//函数具有块级作用域,把当前每次循环的i通过闭包的方式传递给点击事件,因为是立即执行函数,所以每次传递进去执行
//后,在开始下一轮循环,直到最后点击事件循环完毕,虽然是使用var定义的i,但是i在改变前点击事件通过闭包的方式
//传递进去并执行,直接把当前的i定义在当前函数作用域内

 ES6循环

for(let i=0;i<btns.length;i++){
btns[i].addEventListener('click',function(i){
console.log(`第${num}按钮发生点击`);
})
}
//let具有块级作用域的,每次循环时进去不会改变当前传递i的值,因为每次循环都是变量提升和代码预解析
//都是把let提升到当前作用域的最前方

联系我们