计算属性
计算属性的基本使用
我们直到,在模板可以直接通过插值语法显示一些 data 里的数据
但是在某些情况下,我们可能需要对数据先进行转换后再显示,或者需要多个数据结合起来进行显示
- 比如我们有 firstName 和 lastName 两个变量,我们需要显示完整的名称
- 但是如果多个地方都要显示完整的名称,使用以前的方法就需要写多个 {{firstName}} {{lastName}}
因此,为了方便阅读
我们可以将上面的代码换成计算属性:
- 计算属性是写在实例的 computed 选项中的
- 另外我们会发现,虽然我们在 computed 里面定义的是一个方法,但是在调用时并没有显式得调用该方法,Vue 在这里面帮我们做了很多优化。因此,虽然在 methods 中也可以定义相同的方法,但是调用起来会更复杂,而且性能没有 computed 里的高, computed 里的只会调用一次,会有缓存数据,性能会提高。
<div id="app">
<!-- 以前的方法 -->
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: 'Lebron',
lastName: 'James'
},
// computed: 计算属性() 命名最好以属性命名,不用加动词,比如 get..
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName;
}
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
});
</script>
再比如一个案例,我们有一些书,想要展示他们的价格和
data: {
books: [
{id: 110, name: "Unix编程艺术", price: 119},
{id: 111, name: "代码大全", price: 105},
{id: 112, name: "深入理解计算机原理", price: 90},
{id: 113, name: "现在操作系统", price: 89},
]
}
--- ... ---
computed: {
totalPrice: function () {
let result = 0;
for (let i = 0; i < this.books.length; i++) {
result += this.books[i].price;
}
return result;
}
}
这样我们就可以通过 {{totalPrice}} 很方便简洁的得到了总价格
计算属性的 setter 和 getter
data: {
firstName: 'Kobe',
lastName: 'Bryant'
},
computed: {
// 这个方法是缩写的
/*fullName: function () {
return this.firstName + ' ' + this.lastName
}*/
// 属性,属性有 get 和 set 方法
fullName: {
set: function () {
// 一般情况,计算属性不需要 set 方法,只读
// 所以都不设置 set 方法,因为我们一般情况不希望改变值
// 所以一般情况都不写,只写 get 也是不会报错的, 所以 Vue 提供了更加简便的方法设置
// 也就是之前那种写法,这也就是为什么我们可以通过那样得到值,其实是一个 getter 方法
},
get: function () {
return this.firstName + ' ' + this.lastName
}
}
set 的用法如下:
fullName: {
// 我们可以设置 fullName 的值(通过控制台访问该属性并赋值),这个时候就会调用 set 方法
// set 方法通过传递一个参数,将修改后的值传递到 set 方法,我们就可以对 fullName 的真实值进行变更,从而达到设置值的目的
set: function (newValue) {
console.log('----', newValue); // 当 fullName 发生改变时,会调用 set 方法,可以在控制台看到该语句输出
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
},
get: function () {
return this.firstName + ' ' + this.lastName;
}
}
methods 与 computed 的对比
<div id="app">
<!-- 1. 直接拼接:语法过于繁琐 -->
<h2>{{firstName}} {{lastName}}</h2>
<!-- 2. 通过定义 methods,调用四次用于测试 -->
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<!-- 3. 通过 computed,调用四次用于测试 -->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
firstName: 'Kobe',
lastName: 'Bryant'
},
methods: {
getFullName() {
console.log('getFullName'); // 被打印 4 次,调用 4 次
return this.firstName + ' ' + this.lastName;
}
},
computed: {
fullName: function () {
console.log('fullName'); // 只被打印一次,调用 1 次,有做缓存
// 只要 firstName 和 lastName 不变,就不会再调用 fullName 了
return this.firstName + ' ' + this.lastName;
}
}
});
</script>
- 由上面的实验我们可以得出结论,当我们需要对数据进行一些处理后再展示时,我们可以使用 computed ,他会对数据进行缓存,只要基本的数据不发送改变,就不会被调用多次。假设在数据处理时,使用到了多个循环,那么 methods 每调用一次性能都差很多!
ES6 语法补充
let / var
事实上 var 的设计可以看成 JavaScript 语言设计上的错误,但是这种错误多半不能修复和移除,因为需要向后兼容。
所以大概十年前, Brendan Eich 就决定修复这个问题,于是他添加了一个新的关键字: let。
我们可以将 let 看成更完美的 var。
块级作用域
let 是有块级作用域的
JavaScript 中使用 var 来声明一个变量时,变量的作用域主要是和函数的定义有关
针对于其他块定义来说是没有作用域的,比如 if / for,这在我们开发中往往会引起一些问题
<script>
// ES5 中的 var 是没有块级作用域的
// 1. 变量作用域:变量在什么范围内是可用。
{
var name = 'why';
console.log(name)
// 在很多编程语言中,代码块里面的变量,外面是无法访问的
}
console.log(name);// 这咯i也可以访问 name
// 2. 没有块级作用域引起的问题, if 的块级
var func;
if (true) {
var name = 'why';
func = function () {
console.log(name)
}
}
// func 的作用是打印 if 里面 name 的值
// 很多情况下,我们是把 name 封装到 if 等其他代码块里面
// 我们不希望外面的人能够修改他们,但是 var 会让外面也可以访问
name = 'kobe';
func();
</script>
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
<script>
// 2. 没有块级作用域引起的问题, for 的块级
// 假设我们需要,点击第几个按钮,就在控制台打印第几个按钮被点击
// 我们以前会这样做
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// 我们会发现,我们点哪一个按钮,都是第5个按钮被点击
// 因为这个 i 其实是一个全局变量,i 一被更改,所有使用到 i 的地方,都被修改
// 解决办法
for (var i = 0; i < btns.length; i++) {
(function (num) {
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
})(i)
}
// 因为 在 JavaScript 中函数是有作用域的,所以把 i当作一个参数传递进去
// 这样值就不会被随便修改
// 在 ES5 之前,因为 if 和 for 都没有块级作用域的概念,
// 所以在很多时候,我们都必须借助于 function 的作用域来解决应用外面变量的问题
// ES6 中,加入了 let,它在 if 和 for 中有块级作用域的概念
const btns = document.getElementsByTagName('button');
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// 这样就可以成功,并且简洁
</script>
伪代码分析:
<script>
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// 1. 情况一: ES5 中没有使用闭包
{
i = 0;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
i = 1;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
i = 2;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// 因为 var 没有作用域,本来应该使用自己各自的 i
// 结果每一个代码块中的 i 都是一样的
for (var i = 0; i < btns.length; i++) {
(function (num) {
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
})(i)
}
// 2. 情况二: ES5 使用闭包
// 因为函数是有作用域的,所以这个 i 当作一个参数传入,这样每个函数体内部都有一个属于自己的 num
function (num) { // i = 0 -> num = 0
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
}(i)
function (num) { // i = 1 -> num = 1
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
}(i)
function (num) { // i = 2 -> num = 2
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
}(i)
const btns = document.getElementsByTagName('button');
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
// 3. 情况三:ES6 let
{
i = 0; // 每个大括号有属于自己的 i
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
i = 1;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
i = 2;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
</script>
const 的使用
const 关键字在很多语言都存在,主要的作用是将某个变量修饰为常量,不再对它进行更改。
在 JavaScript 也是如此
什么时候使用 const ?
当我们修饰的标识符不会被再次赋值时,可以使用 const 来保证数据的安全性
- 注意一:
const a = 20;
a = 30; // 错误,不可以修改
- 注意二:
const name; // 错误,const 修饰的标识符必须赋值
- 建议:在 ES6 开发中,优先使用 const,只有需要改变某个标识符的时候才使用 let
- 注意三:
// 常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
const obj = {
name: 'why',
age: 18,
height: 1.88
}
console.log(obj);
obj.name = 'kobe';
obj.age = 40;
obj.height = 1.87;
console.log(obj);
保存对象的时候其实是保存对象的地址,所以这个地址不能更改
对象的增强写法
<script>
// 1. 属性的增强写法
const name = 'why';
const age = 18;
const height = 1.88;
// 假设我们需要把 这些属性放进一个对象,之前是这样做的
// ES5 写法
/*
const obj = {
name: name,
age: age,
height: height
}
*/
// ES6 写法, 在内部会把变量名作为 key,变量的值作为 value
const obj = {
name,
age,
height
}
console.log(obj);
// 2. 函数的增强写法
// ES5 写法
/*
const obj1 = {
run: function () {
},
eat: function () {
}
}
*/
// ES6 写法
const obj1 = {
run() {
},
eat() {
}
}
// 这样语法更简单
</script>