javascript高级
JavaScript 进阶 - 第1天
学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。
- 理解作用域对程序执行的影响
- 能够分析程序执行的作用域范围
- 理解闭包本质,利用闭包创建隔离作用域
- 了解什么变量提升及函数提升
- 掌握箭头函数、解析剩余参数等简洁语法
一、作用域
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
1.1 局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
let s = x + y;
console.log(s); // 18
}
// 设用 counter 函数
counter(10, 8);
// 访问变量 s
console.log(s); // 报错
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age); // 报错
let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!';
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 报错
</script>
var let
// var申明的变量不具有块级作用域、let具有块级作用域
// var申明变量,可以重新定义申明的,let不可以
// var申明的变量,相当于window添加属性,let直接就是变量
// var和let都可以只定义不赋值
// 变量提升:后面讲解
// const:const申明的变量不准改变,成为常量
// 其他的和let没区别
// const定义的常量不准该值
// const定义的常量必须初始化赋值
// 建议:
// 1、常量名都用大写,纯大写
// 2、一般固定不变不的值使用常量
// const PI = 3.14;
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
</script>
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域const
声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const
声明成常量。
-
关键字 块级作用域 变量提升 初始值 更改值 通过window调用 let √ ×√ - Yes No const √ ×√ Yes No No var × √ - Yes Yes
1.2 全局作用域
<script>
标签和 .js
文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
</script>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script>
// 全局变量 name
let name = '小明';
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name);
}
// 全局变量 flag 和 x
let flag = true;
let x = 10;
// 块作用域中访问全局
if(flag) {
let y = 5;
console.log(x + y); // x 是全局的
}
</script>
总结:
- 为
window
对象动态添加的属性默认也是全局的,不推荐! - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
1.3 作用域链
在解释什么是作用域链前先来看一段代码:
<script>
// 全局作用域
let a = 1;
let b = 2;
// 局部作用域
function f() {
let c;
// 局部作用域
function g() {
let d = 'yo';
}
}
</script>
函数内部允许创建新的函数,f
函数内部创建的新函数 g
,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
// 作用域链:由作用域串联起来的链状结构
<script>
// 全局作用域
let a = 1;
let b = 2;
// 局部作用域
function f() {
let c;
// let a = 10;
console.log(a); // 1 或 10
console.log(d); // 报错
// 局部作用域
function g() {
let d = 'yo';
// let b = 20;
console.log(b); // 2 或 20
}
// 调用 g 函数
g()
}
console.log(c); // 报错
console.log(d); // 报错
f();
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域(就近原则)
1.4 闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<script>
function foo() {
let i = 0;
// 函数内部分函数
function bar() {
console.log(++i);
}
// 将函数做为返回值
return bar;
}
// fn 即为闭包函数
let fn = foo();
fn(); // 1
</script>
总结:
闭包:一个作用域有权访问另外一个作用域的局部变量,
好处:可以把一个变量使用范围延伸
- 闭包本质仍是函数,只不是从函数内部返回的
- 闭包能够创建外部可访问的隔离作用域,避免全局变量污染
- 过度使用闭包可能造成内存泄漏
注:回调函数也能访问函数内部的局部变量。
1.5 变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
<script>
// 访问变量 str
console.log(str + 'world!');
// 声明变量 str
var str = 'hello ';
</script>
let和var都有提升,但是let定义的变量没有赋值之前是不可以使用、var可以使用是undefined
如果变量声明没有使用 var ,不存在变量提升的
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined
let
声明的变量不存在变量提升,推荐使用let
【也有人认为具有提升但是不赋值不能使用】- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
- 任何一个作用域执行代码之前都要先预解析会把代码中申明的变量,提前解析到当前(提升到)作用域最前面
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let
可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
二、函数
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
// 会把代码中具有名字的函数提前解析,解析到当前作用域最前面
// 但是,只定义,不调用
// 函数优先
2.1 函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
<script>
// 调用函数
foo();
// 声明函数
function foo() {
console.log('声明之前即被调用...');
}
// 不存在提升现象
bar();
var bar = function () {
console.log('函数表达式不存在提升现象...');
}
</script>
总结:
-
函数提升能够使函数的声明调用更灵活
-
函数表达式不存在提升的现象
-
函数提升出现在相同作用域当中
-
函数的优先级高于变量
2.2 预解析优先级
1,预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在var之上。
2,函数执行的时候,函数内部才会进行预解析,如果有参数,先给实参赋值再进行函数内部的预解析。
3,预解析函数是声明+定义(开辟了内存空间,形参值默认是undefined)。
4,预解析变量是只声明,不赋值,默认为undefined。
5,函数重名时,后者会覆盖前者。
6,变量重名时,不会重新声明,但是执行代码的时候会重新赋值。
7,变量和函数重名的时候,函数的优先级高于变量。
使用 function 定义函数的预解析:先告诉解析器这个函数名的存在,然后再告诉解析器这个函数名的函数体是什么 。如下:
console.log(fn);
function fn(){
return "整个函数都会提升到最前面";
}
声明函数会把整个函数都提升到最前面 ,所以结果会输出整个函数,结果如下:
f fn(){
return "整个函数都会提升到最前面";
如果在一个函数作用域中声明一个变量 ,那么它也会提升到函数作用域的最上面,如下:
var a = 1;
function fn(){
console.log(a);
var a = 2;
}
fn();
以上虽然全局作用域声明了一个变量 a ,但是函数里面也声明了一个变量 a ,所以会先查找函数里面是否有变量 a,如果有的话就不会再全局下查找了。函数里面的变量 a 会被提升到函数作用域的最前面 ,并且赋值为 undefined,所以输出结果为 undefined ,类似于如下结构:
var a = 1;
function fn(){
var a;
console.log(a);
a = 2;
}
fn();
函数的参数也可以理解为函数作用域的变量 ,如下:
var a = 1;
function fn(a){
console.log(a); //undefined
}
fn();
console.log(a); //1
为函数 fn 传递一个形参 a ,由于函数在调用时没有传递实参 (也就是说变量 a 没有赋值),所以为undefined 。而在全局下输出a 自然在全局下查找变量a ,结果为 1。
不要在if、for之类的代码块中声明函数,在某些版本的浏览器中预解析会出错。
变量或函数覆盖
如果在同一个作用域下声明两个相同的变量或者函数,那么后一个会覆盖前一个。如下:
var a = 1;
var a = 2;
console.log(a); //2
function x(){
console.log('x');
}
function x(){
console.log('x2');
}
x(); //x2
如果声明的函数与变量名字相同 ,那又会怎么覆盖呢?可以看如下例子:
var m = 1;
function m(){
console.log('x');
}
m(); //error: m is not a function
JavaScript 中 ,函数的预解析优先级是要高于变量的预解析的。所以上面的例子中 ,虽然函数 m 是在变量m 下面定义的 ,但是在预解析时优先解析为函数m,在代码执行时,变量 m 赋值为1,,把前面的函数 m 覆盖,最后 m 为 1 为数值类型 ,所以调用 m 时报错 ,m 不是一个函数。
需要注意的是 ,如果变量 m 定义后没有赋值 ,那么函数就不会被覆盖了,如下:
var m;
function m(){
console.log("11");
}
m(); //11
案例:
console.log(a); //此时,变量a还没赋值,所以a还是函数 function a(){console.log(4)}
var a = 1; //变量a赋值了,a变成1
console.log(a); // 1
function a(){ //函数声明被提升到最前面
console.log(2)
}
console.log(a); //1
var a = 3;
console.log(a); //3
function a(){ //函数声明被提升到最前面
console.log(4)
}
console.log(a); //3
a(); //error: a is not a function
以上例子,两个函数 a 优先提升 ,所以第二个函数 a 覆盖第一个函数 a。然后两个变量 a 提升,由于变量 a 提升后为 undefined,所以第二个函数没有被覆盖 ,第一个输出 a 结果为第二个函数 function a(){console.log(4);}。随后 a 被赋值为 1 ,所以第二个输出 a 结果为 1。因为第一个函数 a 已经被提升到前面去了,所以第三个输出 a 结果还是 1。随后为 a 赋值为 3,所以第四,第五输出 a 结果为 3。最后调用 a,a 因为是数值类型,所以会报错 a 不是一个函数。
2.3 参数
函数参数的使用细节,能够提升函数应用的灵活度。
默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined
- 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
动态参数
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
<script>
// 求生函数,计算所有参数的和
function sum() {
// console.log(arguments);
let s = 0;
for(let i = 0; i < arguments.length; i++) {
s += arguments[i];
}
console.log(s);
}
// 调用求和函数
sum(5, 10); // 两个参数
sum(1, 2, 4); // 两个参数
// arguments 的使用 只有函数才有 arguments对象 而且是每个函数都内置好了这个arguments
function fn() {
// console.log(arguments); // 里面存储了所有传递过来的实参 arguments = [1,2,3]
// console.log(arguments.length);
// console.log(arguments[2]);
// 我们可以按照数组的方式遍历arguments
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
fn(1, 2, 3);
fn(1, 2, 3, 4, 5);
// 伪数组 并不是真正意义上的数组
// 1. 具有数组的 length 属性
// 2. 按照索引的方式进行存储的
// 3. 它没有真正数组的一些方法 pop() push() 等等
</script>
// 案例:求若干个数的最大值
// 建议:
// 如果参数固定写形参,
// 参数不固定用arguments
function getMax () {
// arguments:
// 假设最大值
let max = arguments[0];
// 遍历比较
for (let i = 0; i < arguments.length; i++) {
if ( max < arguments[i] ) max = arguments[i];
}
console.log(max)
}
getMax(23, 66, 78, 123, 4, 9, 3, 12345, 456, 65,67, 34);// 参数不固定11
总结:
arguments
是一个伪数组arguments
的作用是动态获取函数的实参
剩余参数
<script>
function config(baseURL, ...other) {
console.log(baseURL);
// other 是真数组,动态获取实参
console.log(other);
}
// 调用函数
config('http://baidu.com', 'get', 'json');
</script>
//案例求和
function getSum (...a) {
// a
let sum = 0;
for (let i = 0; i < a.length; i++) {// a = [1, 2, 3......]
sum = sum + a[i]
}
console.log(sum);
}
getSum(1, 2, 3, 4, 5, 6);
总结:
...
是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...
获取的剩余实参
2.4 箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
<script>
// 箭头函数
let foo = () => {
console.log('^_^ 长相奇怪的函数...');
}
// 调用函数
foo();
// 更简洁的语法
let form = document.querySelector('form');
form.addEventListener('click', ev => ev.preventDefault());
</script>
// 注意点:
// 1、箭头不存在预解析,所以必须先定义再调用
// let fn = () => {
// console.log('aaa');
// }
// fn();
// 2、箭头函数中,不存在arguments
// let fn = (...a) => {
// console.log(a);
// };
// fn(1, 2, 3);
// 3、箭头函数认为不存在this,箭头函数中的this指向的是上级作用域的this
// 箭头函数中的this指向箭头函数所在作用域的this
// 如果涉及到this使用的时候,劲量不要使用箭头函数
let obj = {
uname : '阿飞',
age : 22,
fei : function () {
// console.log(this);
window.setInterval( () => {
console.log(this)
}, 1000 );
}
} //this指向上一级,obj
obj.fei();
// window.setInterval(function () {
// console.log(this);
// }, 1000) //this指向window
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时可以省略花括号
{}
,并自动做为返回值被返回 - 箭头函数中没有
arguments
,只能使用...
动态获取实参 - 涉及到this的使用,不建议用箭头函数
三、解构赋值
知道解构的语法及分类,使用解构简洁语法快速为变量赋值。
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
3.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法,如下代码所示:
<script>
// 普通的数组
let arr = [1, 2, 3];
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
</script>
// 一一对应
// let [a, b, c, d, e, f] = ['张飞', '赵云', '关羽', '张辽', '许褚', '典韦']
// console.log(a, b, c, d, e, f);
// 变量多值少
// let [a, b, c, d, e, f, g, h, i, j, k,l] = ['张飞', '赵云', '关羽', '张辽', '许褚', '典韦'];
// console.log( a, b, c, d, e, f, g, h, i, j, k, l );
//变量少值多
let [a, b, c,...d] = ['张飞', '赵云', '关羽', '张辽', '许褚', '典韦'];
console.log( a, b, c ,d);
// 按需取值
// let [, , a, b, , c] = ['张飞', '赵云', '关羽', '张辽', '许褚', '典韦']
// console.log( a, b, c );
// 剩余值
// let [, a, b, ...c] = ['张飞', '赵云', '关羽', '张辽', '许褚', '典韦'];
// console.log(a, b, c);
// 复杂情况
// let [, , a, b, [, c, d]] = ['张飞', '赵云', '关羽', '张辽', ['林冲', '鲁智深', '武松', '宋老板']]
// console.log( a, b, c, d );
总结:
- 赋值运算符
=
左侧的[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined
- 变量的数量小于单元值数量时,可以通过
...
获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
3.2 对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
<script>
// 普通对象
let user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let {name, age} = user;
console.log(name); // 小明
console.log(age); // 18
// 把属性名当做变量名即可
// let obj = {
// uname : '张三丰',
// age : 22,
// sex : '男',
// score : 99,
// index : 6,
// }
// let uname = '阿飞';
// // 把对象解开解构赋值给变量
// // 用冒号改名字
// let {uname:userName, sex} = {
// uname : '张三丰',
// age : 22,
// sex : '男',
// score : 99,
// index : 6,
// }
// console.log( userName, sex );
// let { dog:{uname} } = {
// uname : '张三丰',
// dog : {
// uname : '小狗子',
// age : 1,
// },
// age : 22,
// sex : '男',
// score : 99,
// index : 6,
// }
// console.log( uname );
function person ( {uname, age, sex} ) {
// let uname = obj.uname;
// let age = obj.age;
// let sex = obj.sex;
console.log(`我叫${uname}今年${age}岁是${sex}性`);
}
person( {uname : '阿飞', age : 22, sex : '男'} )
</script>
总结:
- 赋值运算符
=
左侧的{}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量
- 对象中找不到与变量名一致的属性时变量值为
undefined
- 允许初始化变量的默认值,属性不存在或单元值为
undefined
时默认值才会生效
注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
JavaScript 进阶 - 第2天
了解面向对象编程的基础概念及构造函数的作用,体会 JavaScript 一切皆对象的语言特征,掌握常见的对象属性和方法的使用。
- 了解面向对象编程中的一般概念
- 能够基于构造函数创建对象
- 理解 JavaScript 中一切皆对象的语言特征
- 理解引用对象类型值存储的的特征
- 掌握包装类型对象常见方法的使用
一、面向对象
了解面向对象的基础概念,能够利用构造函数创建对象。
1.1 对象
// 1、字面量创建对象
let obj = {
// 属性名:属性值,
// 键值对
// 成员
uname : '张三丰',
age : 22,
sex : '男',
taiji : function () {
console.log('打太极');
},
}
// 属性:
// console.log( obj.uname );用于国定属性写法
// console.log( obj['age'] );用于动态属性的写法
// 方法:
// obj.taiji();
// console.log( obj.taiji );
//遍历对象
for ( let key in obj ) {
console.log( obj[key] );
}
let arr = [1, 2, 3];
for (let k in arr) {
console.log( arr[k] );
}
1.2 构造函数
构造函数是专门用于创建对象的函数,如果一个函数使用 new
关键字调用,那么这个函数就是构造函数。
<script>
// 定义函数
function foo() {
console.log('通过 new 也能调用函数...');
}
// 调用函数
new foo;
// 构造函数:其实也是函数,只不过构造函数一般用于和new搭配使用,创建对象
// 内置构造函数:Object,创建对象
// let obj = new Object( {uname : '张三丰', age : 22, sex : '男'} );
// console.log( obj );
// 注意:如果构造函数不需要参数,那么可以省略小括号
//let obj = new Object;
// obj.uname = '张飞';
//console.log(obj);
</script>
总结:
- 使用
new
关键字调用函数的行为被称为实例化 - 实例化构造函数时没有参数时可以省略
()
- 构造函数的返回值即为新创建的对象
- 构造函数内部的
return
返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
1.3 自定义构造函数
区分:构造函数首字母大写
<script type="text/javascript">
// 自定义构造函数
// 建议:所有构造函数的首字母大写
// 构造函数里面this指向当前实力对象
function Person (uname, age, sex) {
// 设置属性
this.uname = uname;
this.age = age;
this.sex = sex;
this.eat = function () {
console.log('吃饭');
};
this.sing = function () {
console.log('唱歌');
}
}
// 一般如何使用
// 实例化对象
let o = new Person('张三丰', 22, '男');
console.log( o );
let o1 = new Person('阿飞', 21, '男');
console.log( o1 )
</script>
1.4 实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
<script>
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
// 实例对象中动态添加属性
this.name = '小明';
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~');
}
}
// 实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
let p1 = new Person();
console.log(p1);
console.log(p1.name); // 访问实例属性
p1.sayHi(); // 调用实例方法
</script>
总结:
-
构造函数内部
this
实际上就是实例对象,为其动态添加的属性和方法即为实例成员 -
为构造函数传入参数,动态创建结构相同但值不同的对象
-
实例对象的
constructor
属性指向了构造函数 -
instanceof
用于检测实例对象对应的构造函数function A () {} function B () {} let obj = new B(); // instanceof:用于判断一个对象是否是另外一个构造函数的实力对象 // 对象 instanceof 构造函数 // console.log( obj instanceof B ); // constructor:指回构造函数本身 console.log( obj.constructor );
注:构造函数创建的实例对象彼此独立互不影响。
1.5 静态成员
在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。
<script>
// 构造函数
function Person(name, age) {
// 省略实例成员
}
// 静态属性
Person.eyes = 2;
Person.arms = 2;
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路...');
// this 指向 Person
console.log(this.eyes);
}
</script>
总结:
- 静态成员指的是添加到构造函数本身的属性和方法
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this
指向构造函数本身
二、一切皆对象
体会 JavaScript 一切皆对象的语言特征,掌握各引用类型和包装类型对象属性和方法的使用。
在 JavaScript 中最主要的数据类型有 6 种,分别是字符串、数值、布尔、undefined、null 和 对象,常见的对象类型数据包括数组和普通对象。其中字符串、数值、布尔、undefined、null 也被称为简单类型或基础类型,对象也被称为引用类型。
在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 Date
就是内置的构造函数。
<script>
// 实例化
let date = new Date();
// date 即为实例对象
console.log(date);
简单数据类型:字符串、数字、布尔、undefined、null
(引用类型)复杂数据类型:对象(数组)
</script>
甚至字符串、数值、布尔、数组、普通对象也都有专门的构造函数,用于创建对应类型的数据。
2.1 引用类型
数据传递
值传递
引用传递
<script type="text/javascript">
// 变量传递给另外一个变量,称为数据传递
// 值传递:会把数据复制一份传递,(简单类型)
// 引用传递:会把数据地址复制一份传递,(引用类型)
// let n = 3;
// let m = n;
// n = 6;
// console.log(n, m);
// 引用类型
let obj = {
uname : '阿飞',
age : 22,
sex : '男'
}
let o = obj;
obj.uname = '二柱子';
console.log( obj, o );
</script>
Object
Object
是内置的构造函数,用于创建普通对象。
<script>
// 通过构造函数创建普通对象
let user = new Object({name: '小明', age: 15});
// 这种方式声明的变量称为【字面量】
let student = {name: '杜子腾', age: 21}
// 对象语法简写
let name = '小红';
let people = {
// 相当于 name: name
name,
// 相当于 walk: function () {}
walk () {
console.log('人都要走路...');
}
}
console.log(student.constructor);
console.log(user.constructor);
console.log(student instanceof Object);
</script>
下图展示了普通对象在内存中的存储方式:普通对象数据保存在堆内存之中,栈内存中保存了普通对象在堆内存的地址。
普能对象在赋值时只是复制了栈内中的地址,而非堆内存中的数据,如下图所示:
普通对象赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变。
总结:
- 推荐使用字面量方式声明对象,而不是
Object
构造函数 Object.assign
静态方法创建新的对象Object.keys
静态方法获取对象中所有属性Object.values
表态方法获取对象中所有属性值
面试回答堆与栈的区别:
- 堆和栈是内存中的数据存储空间
- 简单类型的数据保存在内存的栈空间中
- 引用类型的数据保存在内存的堆空间中,栈内存中存取的是引用类型的地址(房间号)
Array
Array是内置的构造函数,用于创建数组。
<script>
// 构造函数创建数组
let arr = new Array(5, 7, 8);
// 字面量方式创建数组
let list = ['html', 'css', 'javascript'];
console.log(list.constructor);
console.log(list instanceof Array);
</script>
数组在内存中的存储方式与普通对象一样,如下图所示:
数组在赋值时只是复制了栈内中的地址,而非堆内存中的数据,如下图所示:
数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变。
总结:
-
推荐使用字面量方式声明数组,而不是
Array
构造函数 -
实例方法
forEach
用于遍历数组,替代for
循环 -
实例方法
filter
过滤数组单元值,生成新数组 -
实例方法
map
迭代原数组,生成新数组 -
实例方法
join
数组单元素拼接成了符串 -
实例方法
concat
合并两个数组,生成新数组 -
实例方法
sort
对原数组单元值排序 -
实例方法
splice
删除或替换原数组单元 -
实例方法
indexOf
检索数组单元值 -
实例方法
reverse
反转数组 -
静态方法
from
伪数组转成数组
contact方法
// concat:用于拼接为新数组
let arr = [1, 2, 3];
let ary1 = ['a', 'b', 'c', 'd'];
let ary2 = [11, 222, 333];
let reArr = arr.concat(ary1, ary2, '张飞', '关羽', '赵云');
console.log(reArr);
join方法
// join():用于连接数组的每个元素成为字符串
let arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
let str = arr.join('-');
console.log(str);
reverse方法
// reverse:翻转数组顺序
let arr = [1, 2, 3, 4];
let re = arr.reverse();
console.log(re);
lastIndexOf indexOf
// indexOf:查找某个元素在数组中首次出现的索引位置,找不到就是返回-1
let arr = ['a', 'b', 'c', 'd', 'a', 'b', 'f'];
let re = arr.indexOf('m');
console.log(re);
// lastIndexOf:查找某个元素在数组中尾次出现的索引位置,找不到就返回-1
let re1 = arr.lastIndexOf('b');
console.log(re1);
sort
// sort:排序
let arr = [3, 16, 22, 66, 123, 99];
// 正序排列:
let re = arr.sort(function (a, b) {return a - b;});
// 倒序排列
// let re = arr.sort(function (a, b) {return b - a;});
console.log( re );
Array.isArray()
// Array.isArray();
let a = [1, 2, 3];
let re = Array.isArray(a);
console.log(re);
Array.from(伪数组)
// 特别注意:要想把伪数组转换为真数组必须有length属性
let o = {
0 : 'a',
1 : 'b',
2 : 'c',
3 : 'd',
4 : 'e',
5 : 'f',
6 : 'h',
length : 4,
}
let ary = Array.from(o);
console.log( ary );
console.log(Array.from('from'));
//将数组中布尔值为false的成员指为0
Array.from([1, ,2,3,3], x => x || 0) //[1,0,2,3,3]
//将一个类似数组的对象转为一个数组,并在原来的基础上乘以2倍
let arrayLike = { '0': '2', '1': '4', '2': '5', length: 3 }
Array.from(arrayLike, x => x*2) //[4,8,10]
//将一个set对象转为数组,并在原来的基础上乘以2倍
Array.from(new Set([1,2,3,4]), x => x*2) //[2,4,6,8]
forEach
// arr.forEach( function (item, index, o) {
// // 第一个参数:代表数组的每一项(每个元素)
// // 第二个参数:代表数组的每一项的索引值(索引值)
// // 第三个参数:代表当前数组本身
// console.log(item, index, o);
// } );
let arr = [
{uname :'阿飞', age : 22, sex : '男'},
{uname :'张三丰', age : 23, sex : '男'},
{uname :'李寻欢', age : 21, sex : '男'},
{uname :'张三丰1', age : 23, sex : '男'},
{uname :'李寻欢1', age : 21, sex : '男'},
{uname :'张三丰2', age : 23, sex : '男'},
{uname :'李寻欢2', age : 21, sex : '男'},
{uname :'张三丰2', age : 23, sex : '男'},
{uname :'李寻欢2', age : 21, sex : '男'},
];
arr.forEach( item => {
console.log(`姓名:${item.uname},年龄${item.age},性别${item.sex}`);
} );
find
// find:用于查找首次出现的满足条件的值,并返回 没有返回undefined
let re = [2, 6, 4, 7, 9, 3].find( function (item, index, o) {
return item > 5;
} )
console.log(re);
findIndex
// findIndex:用于查找首次出现的满足条件的值,并返回期所在索引值 没有返回-1
let re = [2, 6, 4, 7, 9, 3].findIndex( function ( item, index, o ) {
return item > 50;
} );
console.log(re);
some
// some:用于查找如果有一个满足条件返回true 否则false
let re = [2, 6, 4, 7, 9, 3].some( function (item, index, o) {
return item > 5;
} )
console.log(re);
every
// every:用于查找满足条件的元素,如果都满足返回true,否则就是false
let re = [2, 6, 4, 7, 9, 3].every( function (item, index, o) {
return item > 5;
} );
console.log(re);
filter
// filter:筛选数组把满足条件的元素放到新数组返回
let re = [2, 6, 4, 7, 9, 3].filter( function (item, index, o) {
return item > 5;
} );
console.log(re);
map
// map:遍历数组让每个元素执行一边回调函数,把所有结果放到新数组返回
let re = [2, 6, 4, 7, 9, 3].map( function (item, index, o) {
return item * item;
} );
console.log(re);
RegExp
RegExp
内置的构造函数,用于创建正则表达式。
Regular Expression
<script>
// 构造函数创建正则
let reg = new RegExp('\d', 'i');
// 字面量方式创建正则
// let reg = /(\d)/i;
reg.exec('123');
</script>
总结:
- 推荐使用字面量定义正则表达式,而不是
RegExp
构造函数 RegExp
静态属性 $1、$2、$3、... 获取正则分组单元
补充:当使用构造函数创建正则时有两种写法:
<script>
// 使用 // 定义正则
let reg = new RegExp(/\d/);
// 或者使用 '' 定义正则
// 如果使用引号定义正则时,\d、\s、\w
需要多添加一个 \
let reg1 = new RegExp('\\d');
</script>
2.2 包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法,如下代码举例:
<script>
// 字符串类型
let str = 'hello world!';
// 统计字符的长度(字符数量)
console.log(str.length);
// 数值类型
let price = 12.345;
// 保留两位小数
price.toFixed(2);
</script>
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
String
String
是内置的构造函数,用于创建字符串。
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // false
</script>
总结:
- 推荐使用字面量方式声明字符串,而不是
String
构造函数 - 实例属性
length
用来获取字符串的度长 - 实例方法
split
用来将字符串拆分成数组 - 实例方法
toUpperCase
用于将字母转换成大写 - 实例方法
toLowerCase
用于将字母转换成小写 - 实例方法
slice
用于字符串截取 - 实例方法
indexOf
检测是否包含某字符 - 实例方法
startsWith
检测是否以某字符开头 - 实例方法
endsWith
检测是否以某字符结尾 - 实例方法
replace
用于替换字符串,支持正则匹配 - 实例方法
padStart
固定长度字符开始位置打补丁 - 实例方法
padEnd
固定长度字符结束位置打补丁 - 实例方法
match
用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
Number
Number
是内置的构造函数,用于创建数值。
<script>
// 使用构造函数创建数值
let x = new Number('10');
let y = new Number(5);
// 字面量创建数值
let z = 20;
// 检测是否属于同一个构造函数
console.log(x.constructor === z.constructor);
</script>
总结:
- 推荐使用字面量方式声明数值,而不是
Number
构造函数 - 实例方法
toFixed
用于设置保留小数位的长度
注:Number 也可以当做普通函数使用,这时它的作用是强制转换成数值数据类型。
Boolean
Boolean
是内置的构造函数,用于创建布尔值。
<script>
// 使用构造函数创建布尔类型
let locked = new Boolean('10');
// 字面量创建布尔类型
let flag = true;
// 检测是否属于同一个构造函数
console.log(locked.constructor === flag.constructor);
</script>
总结:
- 推荐使用字面量方式声明布尔值,而不是
Boolean
构造函数
注:Boolean 也可以当做普通函数使用,这时它的作用是强制转换成布尔类型数据,由其它数据类型转换成布尔类型的数据被称为真值(truly)或假值(falsly)。
2.3 写在最后
至此对 JavaScript 有了更深的理解,即 JavaScript 中一切皆为对象,还有以前学习的 window、Math 对象,最后补充一点无论是引用类型或是包装包类型都包含两个公共的方法 toString
和 valueOf
<script>
// 对象类型数据
let user = {name: '小明', age: 18}
// 数值类型
let num = 12.345;
// 字符串类型
let str = 'hello world!';
str.valueOf(); // 原始值
user.toString(); // 表示该对象的字符串
</script>
总计:
valueOf
方法获取原始值,数据内部运算的基础,很少主动调用该方法toString
方法以字符串形式表示对象