-
Vue Scoped CSS覆盖组件的样式
如果在 style
标签设置 scoped
属性,则它的样式只应用到当前组件的元素中,直接覆盖子组件的样式是行不通的。如果不使用 scoped
,则可以覆盖,但是这样就变成了全局样式。如果只想在当前组件中应用这个状态呢?如果你希望 scoped
样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>>
操作符:
<style scoped> .a >>> .b { /* ... */ } </style>
上述代码将会编译成:
.a[data-v-f3f3eg9] .b { /* ... */ }
有些像 Sass 之类的预处理器无法正确解析 >>>
。这种情况下你可以使用 /deep/
操作符取而代之——这是一个 >>>
的别名,同样可以正常工作。
Vue文档链接:
-
break语句和continue语句
break
语句和continue
语句都具有跳转作用,可以让代码不按既有的顺序执行。
break
语句用于跳出代码块或循环。
var i = 0;
while(i < 100) {
console.log('i 当前为:' + i);
i++;
if (i === 10) break;
}
上面代码只会执行10次循环,一旦i等于10,就会跳出循环。
for循环也可以使用break语句跳出循环。
for (var i = 0; i < 5; i++) {
console.log(i);
if (i === 3)
break;
}
// 0
// 1
// 2
// 3
continue
语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
var i = 0;
while (i < 100){
i++;
if (i % 2 === 0) continue;
console.log('i 当前为:' + i);
}
上面代码只有在i为奇数时,才会输出i的值。如果i为偶数,则直接进入下一轮循环。
如果存在多重循环,不带参数的break语句和continue语句都只针对最内层循环。
break
语句和continue
语句配合使用,跳出特定的循环。
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
上面代码为一个双重循环区块,break
命令后面加上了top
标签(注意,top
不用加引号),满足条件时,直接跳出双层循环。如果break
语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) continue top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=2, j=0
// i=2, j=1
// i=2, j=2
上面代码中,continue
命令后面有一个标签名,满足条件时,会跳过当前循环,直接进入下一轮外层循环。如果continue
语句后面不使用标签,则只能进入下一轮的内层循环。
JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用Boolean函数。
除了undefined
,null
,+0或-0
,NaN
,''
(空字符串),其他都是自动转为true
将表达式转换为布尔值:
// 写法一
expression ? true : false
// 写法二
!! expression
-
Error实例对象是最一般的错误类型
JavaScript 还定义了其他6种错误对象。也就是说,存在Error的6个派生对象:SyntaxError
,ReferenceError
,RangeError
,TypeError
,URIError
,EvalError
。
throw
语句的作用是手动中断程序执行,抛出一个错误。
if (x < 0) {
throw new Error('x 必须为正数');
}
// Uncaught ReferenceError: x is not defined
throw
也可以抛出自定义错误。
function UserError(message) {
this.message = message || '默认信息';
this.name = 'UserError';
}
throw new UserError('出错了!');
// Uncaught UserError {message: "出错了!", name: "UserError"}
-
try…catch 结构
一旦发生错误,程序就中止执行了。JavaScript
提供了try...catch
结构,允许对错误进行处理,选择是否往下执行。
try {
throw new Error('出错了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: 出错了!
// at <anonymous>:3:9
// ...
上例中,try
代码块抛出错误,js
引擎转而执行catch
代码块,catch
接受一个参数,表示try代码块抛出的值。
可以看出,try...catch
代码块可以用来检测代码是否会抛出错误
try {
f();
} catch(e) {
// 处理错误
}
上面代码中,如果函数f
执行报错,就会进行catch
代码块,接着对错误进行处理。
catch
代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。
finally
代码块:
try...catch
结构允许在最后添加一个finally
代码块,表示不管是否出现错误,都必需在最后运行的语句。
function cleansUp() {
try {
throw new Error('出错了……');
console.log('此行不会执行');
} finally {
console.log('完成清理工作');
}
}
cleansUp()
函数
- 函数声明
采用函数表达式声明函数时,function
命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。
var f = function f() {};
函数的表达式需要在语句的结尾加分号,表示语句结束。而函数的声明不用。
- 构造函数
你可以传递任意数量的参数给Function
构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。
var foo = new Function(
'return "hello world"'
);
// 等同于
function foo() {
return 'hello world';
}
Function
构造函数可以不使用new
命令,返回结果完全一样。
return语句
JavaScript
引擎遇到return
语句,就直接返回return
后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return
语句所带的那个表达式,就是函数的返回值。return
语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
。
- 中断一个函数的执行
function counter() {
for (var count = 1; ; count++) { // 无限循环
console.log(count + "A"); // 执行5次
if (count === 5) {
return;
}
console.log(count + "B"); // 执行4次
}
console.log(count + "C"); // 永远不会执行
}
counter();
// 1A
// 1B
// 2A
// 2B
// 3A
// 3B
// 4A
// 4B
// 5A
- 返回一个函数
function magic(x) {
return function calc(x) { return x * 42};
}
var answer = magic(); //magic()就相当于calc()
answer(1337); // 56154
递归
函数可以调用自身,这就是递归。通过递归,计算斐波那契数列:
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
第一等公民
JavaScript
语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
函数作用域
-
作用域(scope)指的是变量存在的范围。在
ES5
规范中,Javascript
只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。 - 对于
var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。 - 函数内部也存在变量提升,
var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。 - 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
上面代码将函数x
作为参数,传入函数y
。但是,函数x
是在函数y
体外声明的,作用域绑定外层,因此找不到函数y的内部变量a
,导致报错。
同样的,函数体内部声明的函数,作用域绑定函数体内部:
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
-
JavaScript
最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写。这对代码的模块化和重复使用,非常不利。
因此,建议避免使用全局变量。如果不得不使用,可以考虑用大写字母表示变量名,这样更容易看出这是全局变量,比如UPPER_CASE
。 - 自增(
++
)和自减(--
)运算符,放在变量的前面或后面,返回的值不一样,很容易发生错误。事实上,所有的++
运算符都可以用+= 1
代替。
++x
// 等同于
x += 1;
-
switch...case
结构
switch...case
结构要求,在每一个case
的最后一行必须是break
语句,否则会接着运行下一个case
。这样不仅容易忘记,还会造成代码的冗长。而且不使用大括号,不利于代码形式的统一。此外,这种结构类似于goto
语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。
function doAction(action) {
switch (action) {
case 'hack':
return 'hack';
break;
case 'slash':
return 'slash';
break;
case 'run':
return 'run';
break;
default:
throw new Error('Invalid action.');
}
}
-
console
对象与控制台
console
对象是JavaScript
的原生对象,可以输出各种信息到控制台,并且还提供了很多有用的辅助方法。
console
的常见用途有两个:
1、调试程序,显示网页代码运行时的错误信息。
2、提供了一个命令行接口,用来与网页代码互动。
console.log
方法用于在控制台输出信息。它可以接受一个或多个参数,将它们连接起来输出。
console.log('Hello World')
// Hello World
console.log('a', 'b', 'c')
// a b c
console.log
方法支持以下占位符,不同类型的数据必须使用对应的占位符。
-
%s
字符串 -
%d
整数 -
%i
整数 -
%f
浮点数 -
%o
对象的链接 -
%c
CSS 格式字符串
var number = 11 * 9;
var color = 'red';
console.log('%d %s balloons', number, color);
// 99 red balloons
使用%c
占位符时,对应的参数必须是 CSS 代码,用来对输出内容进行CSS渲染。
console.log(
'%cThis text is styled!',
'color: red; background: yellow; font-size: 24px;'
)
console.warn()
,console.error()
也是在控制台输出信息,warn
方法输出信息时,在最前面加一个黄色三角,表示警告;error
方法输出信息时,在最前面加一个红色的叉,表示出错。同时,还会高亮显示输出文字和错误发生的堆栈。
console.error('Error: %s (%i)', 'Server is not responding', 500)
// Error: Server is not responding (500)
console.warn('Warning! Too few nodes (%d)', document.childNodes.length)
// Warning! Too few nodes (1)
对于某些复合类型的数据,console.table
方法可以将其转为表格显示。
var languages = [
{ name: "JavaScript", fileExtension: ".js" },
{ name: "TypeScript", fileExtension: ".ts" },
{ name: "CoffeeScript", fileExtension: ".coffee" }
];
console.table(languages);
console.count()
用于计数,输出它被调用了多少次
function greet(user) {
console.count();
return 'hi ' + user;
}
greet('bob')
// : 1
// "hi bob"
greet('alice')
// : 2
// "hi alice"
greet('bob')
// : 3
// "hi bob"
上面代码每次调用greet
函数,内部的console.count
方法就输出执行次数。
该方法可以接受一个字符串作为参数,作为标签,对执行次数进行分类。上面代码根据参数的不同,显示bob
执行了两次,alice
执行了一次。
console.dir()
,console.dirxml()
dir方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。
console.log({f1: 'foo', f2: 'bar'})
// Object {f1: "foo", f2: "bar"}
console.dir({f1: 'foo', f2: 'bar'})
// Object
// f1: "foo"
// f2: "bar"
// __proto__: Object
上面代码显示dir
方法的输出结果,比log
方法更易读,信息也更丰富。
该方法对于输出 DOM
对象非常有用,因为会显示 DOM
对象的所有属性。
Node
环境之中,还可以指定以代码高亮的形式输出。
console.dir(obj, {colors: true})
dirxml
方法主要用于以目录树的形式,显示 DOM 节点。
console.dirxml(document.body)
更多关于console对象和控制台命令行API: https://wangdoc.com/javascript/features/console.html
-
面向对象编程
- 构造函数
典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。所谓“类”就是对象的模板,对象就是“类”的实例。但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数
(constructor)和原型链
(prototype)。
构造函数就是一个普通的函数,但是有自己的特征和用法。
上面代码中,var Vehicle = function () { this.price = 1000; };
Vehicle
就是构造函数。为了与普通函数区别,构造函 数名字的第一个字母通常大写。
构造函数的特点:- 函数体内部使用了this关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用new命令。
-
new
命令
new
命令的作用,就是执行构造函数,返回一个实例对象。
如果不使用new
,直接调用构造函数,这种情况下,构造函数就变成了普通函数,并不会生成实例对象。而且this
这时代表全局对象,将造成一些意想不到的结果。
- 构造函数
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v // undefined
price // 1000
上面代码中,调用Vehicle
构造函数时,忘了加上new
命令。结果,变量v
变成了undefined
,而price
属性变成了全局变量。因此,应该非常小心,避免不使用new
命令、直接调用构造函数。
为了保证构造函数必须与new
命令一起使用,一个解决办法是,构造函数内部使用严格模式,即第一行加上use strict
。这样的话,一旦忘了使用new
命令,直接调用构造函数就会报错。
function Fubar(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
Fubar()
// TypeError: Cannot set property '_foo' of undefined
new
命令的原理
使用new
命令时,它后面的函数依次执行下面的步骤。
1.创建一个空对象,作为将要返回的对象实例。
2.将这个空对象的原型,指向构造函数的prototype属性。
3.将这个空对象赋值给函数内部的this关键字。
4.开始执行构造函数内部的代码。
也就是说,构造函数内部,this
指的是一个新生成的空对象,所有针对this
的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。
如果构造函数内部有return
语句,而且return
后面跟着一个对象,new
命令会返回return
语句指定的对象;否则,就会不管return
语句,返回this
对象。
var Vehicle = function () {
this.price = 1000;
return 1000;
};
(new Vehicle()) === 1000
// false
上面代码中,构造函数Vehicle
的return
语句返回一个数值。这时,new
命令就会忽略这个return
语句,返回“构造”后的this
对象。
但是,如果return
语句返回的是一个跟this
无关的新对象,new
命令会返回这个新对象,而不是this
对象。这一点需要特别引起注意。
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
上面代码中,构造函数Vehicle
的return
语句,返回的是一个新对象。new
命令会返回这个对象,而不是this
对象。
另一方面,如果对普通函数(内部没有this关键字的函数)使用new
命令,则会返回一个空对象。
function getMessage() {
return 'this is a message';
}
var msg = new getMessage();
msg // {}
typeof msg // "object"
this
关键字
不理解它的含义,大部分开发任务都无法完成。
之前已经提到,this
可以用在构造函数之中,表示实例对象。除此之外,this
还可以用在别的场合。但不管是什么场合,this
都有一个共同点:它总是返回一个对象。
简单说,this
就是属性或方法“当前”所在的对象。
this.property
上面代码中,this
就代表property
属性当前所在的对象。
下面是一个实际的例子。
var person = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
person.describe()
// "姓名:张三"
上面代码中,this.name
表示name
属性所在的那个对象。由于this.name
是在describe
方法中调用,而describe
方法所在的当前对象是person
,因此this
指向person
,this.name
就是person.name
。
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this
的指向是可变的。
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;
B.describe()
// "姓名:李四"
上面代码中,A.describe
属性被赋给B,于是B.describe
就表示describe
方法所在的当前对象是B
,所以this.name
就指向B.name
。
稍稍重构这个例子,this
的动态指向就能看得更清楚。
function f() {
return '姓名:'+ this.name;
}
var A = {
name: '张三',
describe: f
};
var B = {
name: '李四',
describe: f
};
A.describe() // "姓名:张三"
B.describe() // "姓名:李四"
上面代码中,函数f
内部使用了this关键字,随着f
所在的对象不同,this
的指向也不同。
只要函数被赋给另一个变量,this
的指向就会变。
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var name = '李四';
var f = A.describe;
f() // "姓名:李四"
上面代码中,A.describe
被赋值给变量f
,内部的this
就会指向f
运行时所在的对象(本例是顶层对象)。
再看一个网页编程的例子。
<input type="text" name="age" size=3 onChange="validate(this, 18, 99);">
<script>
function validate(obj, lowval, hival){
if ((obj.value < lowval) || (obj.value > hival))
console.log('Invalid Value!');
}
</script>
上面代码是一个文本输入框,每当用户输入一个值,就会调用onChange
回调函数,验证这个值是否在指定范围。浏览器会向回调函数传入当前对象,因此this
就代表传入当前对象(即文本框),然后就可以从this.value
上面读到用户的输入值。
总结一下,JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。
实质:
由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context
)。所以,this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
使用场合
(1)全局环境
全局环境使用this
,它指的就是顶层对象window
。
this === window // true
function f() {
console.log(this === window);
}
f() // true
上面代码说明,不管是不是在函数内部,只要是在全局环境下运行,this
就是指顶层对象window
。
(2)构造函数
构造函数中的this
,指的是实例对象。
var Obj = function (p) {
this.p = p;
};
上面代码定义了一个构造函数Obj
。由于this
指向实例对象,所以在构造函数内部定义this.p
,就相当于定义实例对象有一个p
属性。
var o = new Obj('Hello World!');
o.p // "Hello World!"
不使用new
关键字,this
指向会发生改变(见前面例子)
(3)对象的方法
如果对象的方法里面包含this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this
的指向。