文章目录
js语法进阶笔记
1. 变量进阶
- var申明的变量不具有块级作用域、let具有块级作用域,var申明变量,可以重新定义申明的,let不可以,var申明的变量,相当于window添加属性,let直接就是变量,var和let都可以只定义不赋值,常用let
- const:const申明的变量不准改变,成为常量,const定义的常量不准更改值, const定义的常量必须初始化赋值,其他的和let没区别。建议: 1、常量名都用大写,纯大写 2、一般固定不变的值使用常量
- 作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,先找到谁用谁,如果里面作用域重新声明,则会覆盖声明的,就是一级一级向上直到先找到声明的就用谁
- 闭包,常用场景如下,father成为闭包函数 :闭包:一个作用域有权访问另外一个作用域的局部变量,作用:延申变量的使用范围,尽量少用
function father () {
let num = 66;
// 闭包:返回一个函数,可以访问到这个函数作用域的变量
return function () {
console.log(num)
}
}
let o = father();
o();
- 下面写法不会输出,如果num在function中进行了传入,那么才会输出,
执行不是把参数函数拉来进来执行,而是在当前所在的位置调用执行。
function fun(a) {
let num = 123;
a()
}
fun(function () { console.log(num) })
- var声明的变量,代码执行之前先要预解析,会把代码中声明的变量,提前解析到当前(提升到)作用域最前面,然后再从上到下执行,但是,只定义,不赋值也成为变量提升,let声明的有预解析,但是还会报错,因为let的变量声明执行后才能使用,也可以理解为没有预解析,const和let一样。
- 函数提升: 会把代码中具有名字的函数提前解析,解析到当前作用域最前面但是,只定义,不调用,函数优先,函数表达式不存在提升的现象,函数表达式只会解析那个变量。
2. 函数进阶
- 函数的参数可以在定义的时候给形参一个默认值,NaN不能与NaN
- 动态参数,函数里面内置一个arguments参数,不用特别去写,用于接受函数传过来的所有参数,是一个伪数组。
- 剩余参数:只能在最后一个参数前加三个点,用于接受剩下没复制的参数,也是一个数组
- 箭头函数:写法如下,省略了function,如果小括号里只有一个参数可以省略小括号,没有参数不可以省略。如果函数体只有一行代码,那么可以省略大括号, 如果把大括号省略的话,会自动的返回结果,不需要写return:
let fn = (a, b) => {
return a + b;
}
let re = fn(1, 2);
- (1)箭头不存在预解析,所以必须先定义再调用,如上面的写法
(2)箭头函数中,不存在arguments
(3)箭头函数认为不存在this,箭头函数中的this指向的是上级作用域的this,箭头函数中的this指向箭头函数所在(函数所在,并不是函数里面)作用域的this,如果涉及到this使用的时候,尽量不要使用箭头函数,其它函数的this就是谁调用就指向谁 - var a=b=c=9,等价var a=9; b=9; c=9,如果b,c没有声明默认就是全局,在进行预解析的时候,也只会预解析a
3. 解构赋值
- 解构赋值:解开数据解构赋值给变量
- 数组结构赋值:一一对应,如下所示,变量多,多的为undefined,变量少,一一对应即可,可以用逗号占位得到自己想要的变量,取出剩余的变量也是最后一个前面加… :
let [ a, b, [, c, d]] = ['张飞', '赵云', '关羽', '张辽', ['林冲', '鲁智深', '武松', '宋老板']]
- 对象结构,需要什么变量拿出谁的名字即可,不必一一对应,如果多个对象,如下用dog:{uname},如果防止变量冲突想要进行变量重命名,可以用如uname:userName:
// 把属性名当做变量名即可
let { uname:userName,dog:{uname} } = {
uname : '张三丰',
dog : {
uname : '小狗子',
age : 1,
},
age : 22,
sex : '男',
score : 99,
index : 6,
}
console.log(userName, uname)//张三丰 小狗子
4. 构造函数和对象
- 面向过程:过程,适用于小项目
面向对象:对象,适用于大项目 - 类是泛指概念,对象是类中的具体事物,对象是属性和方法的集合体
- 之前常用的叫做字面量创建对象,注意遍历对象只能使用【key】的形式,不能用点,遍历也可以使用for in的方法,如下所示:
//创建
let obj = {
// 属性名:属性值,
// 键值对
// 成员
uname: '张三丰',
age: 22,
sex: '男',
taiji: function () {
console.log('打太极');
},
}
//遍历
for (let k in obj) {
console.log( arr[k] );
}
- 构造函数创建对象:
(1)构造函数:其实也是函数,只不过构造函数一般用于和new搭配使用,创建对象,常用let obj = new Object( {uname : ‘张三丰’, age : 22, sex : ‘男’} ),如果构造函数不需要参数,那么可以省略小括号,也可以另外在外面给变量的属性赋值,对象里面的成员是没有顺序之分的。
(2)建议:所有构造函数的首字母都大写,写法如下,构造函数里面this指向当前实例对象:
function Person (uname, age, sex) {
// 设置属性
this.uname = uname;
this.age = age;
this.sex = sex;
this.eat = function () {
console.log('吃饭');
};
}
// 实例化对象,返回的是一个对象,未赋值里面也有这个属性
let o = new Person('张三丰', 22, '男');
console.log( o.uname );
function A() { }
function B() { }
let obj = new B();
// instanceof:用于判断一个对象是否是另外一个构造函数的实力对象
// 对象 instanceof 构造函数
console.log( obj instanceof B );//true
// constructor:指回构造函数本身
console.log( obj.constructor );//ƒ B() { }
- 每次new都会出来一个新的对象不会互相影响
- (1)我们把在构造函数名身上直接添加的成员,称为静态成员(静态属性静态方法),静态成员只能由构造函数访问
(2)构造函数内部为实例对象准备的成员,称为实例成员(实例属性和实例方法),只能由实例对象访问
(3) 虽然实例成员在构造函数{ }里面,但是不能直接用构造函数.里面的成员 - js中万物皆对象,简单数据类型也可以认为广义的对象,对象(数组)称为复杂数据类型,也成为引用类型
- 引用数据类型传数据(如对象名=对象名)是地址传递,简单数据类型是值传递(互不影响)
5. 构造数组类型
- 数组和对象和正则除了之前常用的字面量创建,还可以用以下的方式进行创建:
let obj = new Object( {uname : '阿飞', age : 22, sex : '男'} );
// 获取对象的索引值,返回的是一个数组
let re = Object.values(obj);
//任何一个数组都是Array构造函数的实例化对象
let ary = new Array(1, 2, 3);
// 字面量:
let reg = /abc/;
// 构造函数:
let reg1 = new RegExp(/abc/);
- 数组常用的属性和方法参考下述链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array
常用的几个数组方法总结如下:
//concat:用于拼接为新数组,注意返回的是一个新数组
let arr = [1, 2, 3];
let ary1 = ['a', 'b'];
let reArr = arr.concat(ary1, '张飞', '关羽');
console.log(reArr);//[1, 2, 3, 'a', 'b', '张飞', '关羽']
console.log(arr)//[1, 2, 3]
//join():用于连接数组的每个元素成为字符串
let arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
let str = arr.join('-');//空字符串代表直接相连
console.log(str);//a-b-c-d-e-f-g-h
//reverse:翻转数组顺序
let arr = [1, 2, 3];
let re = arr.reverse();
console.log(re);//[3, 2, 1]
//indexOf:查找某个元素在数组中首次出现的索引位置(从01开始),找不到就是返回-1
let arr = ['a', 'b', 'c', 'd', 'a', 'b', 'f'];
let re = arr.indexOf('m');
console.log(re);//-1
//lastIndexOf:查找某个元素在数组中尾次出现的索引位置,找不到就返回-1
let re = arr.lastIndexOf('b');
console.log(re)//5
// 正序排列(里面的函数固定写法,不写的话就按照编码的值逐位比较大小了):
let re = arr.sort(function (a, b) { return a - b; });
// 倒序排列
let re = arr.sort(function (a, b) {return b - a;});
//Array.from(伪数组名)伪数组转真数组
let lis = document.querySelectorAll('li');
let arr = Array.from(lis);
**//forEach:用于遍历数组(3个参数可以省略)**
let arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
arr.forEach(function (item, index, o) {
// 第一个参数:代表数组的每一项(每个元素)
// 第二个参数:代表数组的每一项的索引值(索引值)
// 第三个参数:代表当前数组本身(单独输出会输出数组长度个,因为要遍历完)
console.log(item, index, o);
});
**//forEach常用写法:后面的几个方法都有遍历的功能**
arr.forEach(item => {
console.log(`姓名:${item.uname},年龄${item.age},性别${item.sex}`);
});
// find:用于查找首次出现的满足条件的值,并返回,没有返回undefined
let re = [2, 6, 4, 7, 9, 3].find( function (item, index, o) {
return item > 5;
} )
console.log(re);//6
// findIndex:用于查找首次出现的满足条件的值,并返回期所在索引值,没有返回-1
let re = [2, 6, 4, 7, 9, 3].findIndex( function ( item, index, o ) {
return item > 5;
} );
console.log(re);//1
// some:用于查找如果有一个满足条件返回true
let re = [2, 6, 4, 7, 9, 3].some( function (item, index, o) {
return item > 5;
} )
console.log(re);//true
// every:用于查找满足条件的元素,如果都满足返回true,否则就是false
let re = [2, 6, 4, 7, 9, 3].every( function (item, index, o) {
return item > 5;
} );
console.log(re);//false
// filter:筛选数组把满足条件的元素放到新数组返回
let re = [2, 6, 4, 7, 9, 3].filter( function (item, index, o) {
return item > 5;
} );
console.log(re);// [6, 7, 9]
// map:遍历数组让每个元素执行一边回调函数,把所有结果放到新数组返回
let re = [2, 6, 4, 7, 9, 3].map(function (item, index, o) {
return item * item;
});
console.log(re);//[4, 36, 16, 49, 81, 9]
6. 包装类型
-
在 JavaScript 中的字符串、数值、布尔这三个简单数据类型具有对象的使用特征,如具有属性和方法,之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
-
字符串常用属性见如下参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String
常用的字符串方法如下:
let str = 'abcdefgabcd';
//属性:length(获取长度)
console.log( str.length );//11
console.log( str[1] );
for (let i = 0; i < str.length; i++) {
console.log( str[i] );
}
//trim:用于去除字符串两端(注意只是两端的)空白
let str = ' abc defgabcd ';
console.log(str);
console.log(str.trim());//abc defgabcd
let str = 'abcdefgabcd';
// split:分割字符串,如果里面是空字符串,则依次分割
let re = str.split('c');// ['ab', 'defgab', 'd']
console.log(re);
let str = 'abcdeFGabcd';
// toLowerCase:把字母转成小写
// let re = str.toLowerCase();
// toUpperCase:把字母转成大写
let re = str.toUpperCase();
console.log(re);//ABCDEFGABCD
let str = 'abcdefgabcd';
// indexOf:查找某个元素首次出现的索引值,找不到就是-1
// let re = str.indexOf('mm');//-1
// lastIndexOf:查找某个元素尾次出现的索引值,找不到就是-1
let re = str.lastIndexOf('cd')
console.log(re);//9
//字符串截取,索引从0开始:
//注意:如果只有一个参数,那么从这个索引值位置开始一直截取到最后
let str = 'abcdefgabcd';
//slice:截取字符串
//字符串.slice(start,end);从start索引值位置截取字符串截取到end索引位置
//注意:不包含end
let re = str.slice(1, 4);//bcd
//substring:截取字符串
//字符串.substring(start,end)从start索引值位置截取字符串截取到end索引位置
//注意:end索引位置上的字符取不到,相比于slice,可以自动识别大小值
let re = str.substring(4, 1);//bcd
//substr:截取字符串
//字符串.substr(start, length)从start索引位置开始截取字符串,截取length个
let re = str.substr(1, 4);
console.log(re)//bcde
- number构造函数,parseInt、parFloat最好前面加一个Number,因为新版本把这些加到了Number下面
let num = 66;
console.log(num instanceof Number);//false
let num = new Number(67);
console.log(num instanceof Number);//true
//方法 :
//toFixed(number):用于保留几位有效数字(会进行四舍五入)
let price = 66.36875123;
price = price.toFixed(3);
console.log(price);//66.369
- Boolen 构造数组类型
//字面量构造
let flag = true;
//对象构造
let flag = new Boolean(true);
console.log(flag);
- 所有的数据都有toString,valueOf()获取原始值,除了undefined和null
7. 封装与原型
- 封装的本质是将具有关联的代码组合在一起,其优势是能够保证代码复用且易于维护,函数是最典型也是最基础的代 码封装形式,面向对象思想中的封装仍以函数为基础,但提供了更高级的封装形式。如之前的实例对象和静态对象就的例子,就是封装
- 每一个构造函数都有一个属性prototype、这个属性指向了一个对象,称为原型对象或者原型(原型就是原型,一个新的对象,不与其它的冲突)当**实例对象找不到成员的时候就会找原型对象上的成员,**原型上的对象每个实例对象都可以进行共享调用。
- 注意实例对象可以直接使用对象名.原型的属性,如果这样后面还进行了赋值的操作,那么就是进行了自己内部的创建对象,不会改变原型里面的方法。
- 每一个原型都有一个属性constructor指向构造函数,实例对象里面没有constructor属性,但是他能使用,因为他使用的是原型里面的该属性
- (了解),每个实例对象都有一个__proto__属性,指向原型,因此如果找不到实例成员的时候。就会去原型里面进行寻找
8. 继承
- 简单说:把公共部分写为字面量对象,然后把其赋值给实例对象的原型,然后构造函数的实例对象就都有了这个属性了,但是,此时的因为我们把构造函数的原型重新赋值了,因此构造函数的原型没有constructor属性,只需把该属性重新赋值给自己即可
- 因此,继承分两部,一、该原型 。二、重新赋值constructor(第二步多个赋值时会覆盖)
- obj直接等于一个对象是覆盖,但是如果点运算,会添加
- 为了解决2里面的步骤二的覆盖问题,我们通常把公共的属性放到一个新的构造函数里面,然后去new这个对象,写法如下:
// 公共属性的构造函数
function Person () {
this.head = 1;
this.eyes = 2;
this.legs = 2;
this.say = function () {console.log('say');}
this.eat = function () {console.log('eat');}
}
//新的构造函数
function Chinese () {
this.language = '汉语';
this.skin = 'yellow';
}
// 1、把对象赋值给原型对象
Chinese.prototype = new Person();
// 2、指回构造函数本身
Chinese.prototype.constructor = Chinese;
// 实例对象
let c1 = new Chinese();
console.log(c1);//c1就可以访问到公共和自己的所有成员
- 原型链,有原型构成的链状结构,只要能联通,属性就能用,并且是就近原则:
- 方法里面的this指向调用者
- 对象定义的时候虽然key不加引号,但其是字符串。obj[1],如果有1或者’1’都是可以的,其它非数字的情况下中括号记得加引号访问,进行点运算不能是数字,全部不能加引号
9. this总结
指向小结
- 谁调用指向谁,具体如下示例:
//1、普通函数:无明显是window,有是调用者
function fn () {
console.log(this);//window
}
fn();
function fn() {
console.log(this);//document
}
document.addEventListener('click', fn);
//2、构造函数:实例化对象
function Perosn (uname, age) {
this.uname = uname;
this.age = age;
console.log(this);
}
let obj1 = new Perosn('阿飞', 22);
console.log(obj1);//指向obj1
let obj2 = new Perosn('李寻欢', 23);
console.log(obj2);//指向obj2
//3、对象调用方法:调用者
let obj = {
uname: '张三丰',
age: 22,
fei: function () {
console.log(this);//指向obj
}
}
obj.fei();
//4、事件处理函数:事件源如下是document,或者是标签对象等
document.addEventListener('click', function () {
console.log(this);//document
});
// 5. 定时器函数,立即指向函数(自调用函数)指向window,可省略
window.setInterval(function () {
console.log(this);//window
}, 1000)
;(function () {
console.log(this);//
})()
//6.箭头函数认为不存在this,箭头函数中的this指向的是上级作用域的this,尽量少用
document.addEventListener('click', function () {
window.setInterval(() => {
console.log(this);//document
}, 1000)
});
- 特殊情况,js的两种模式,普通模式和严格模式:开启严格模式:“use strict”;(注意加引号的)"use strict"如果这句话放到全局的开头,全局都要严格模式 "use strict"如果整句话放到了函数的开头,代表这个函数内部严格,其他地方严格:
影响:(1)、变量必须定义再使用
(2)、普通函数中的this指向undefined
(3)、函数的形参不准重名
改变函数的this
- call 方法:
// 函数.call(this,arg1, arg2,......)
// 函数调用call、call会调用函数执行,在执行的过程中,改变this的指向
function fun (a, b) {
console.log(this, a, b)//obj,1,2//初始情况下是指向window的
}
let obj = {uname : '张三丰', age : 22};
fun.call(obj, 1, 2);
//再次调用fun(),还是会指向window,直再执行call的时候会改变
- apply方法:
//apply:函数.apply(this, [arg1, arg2,......]),数组里面的会对应进行一一对应传参,其它和all一摸一样
function fn (a, b) {
console.log(this, a, b);//obj 111 222
}
let obj = {uname : '阿飞'};
fn.apply(obj, [111, 222]);
//常用的场景,如求解数组里的最大值
//方法一:
let arr = [23, 66, 33, 19, 4, 7];
let re = Math.max.apply(null, arr);
console.log( re );//66
//方法二:... rest参数:剩余参数,函数,解构赋值
//... 扩展运算符:数组,字符串,对象
let arr = [23, 66, 33, 19, 4, 7];
// ...扩展运算符
console.log(...arr)//23 66 33 19 4 7
let re = Math.max(...arr);
console.log(re);//66
- bind方法,与上面两个的区别,只会改变this的指向,不会执行函数:
// bind:函数.bind(this, arg1, arg2,......);
function fn (a, b) {
console.log(this, a, b);
}
let obj = {uname : '李寻欢', age : 22};
//fn.bind(obj)//无输出
//fn.bind(obj, 123, 456)();//obj 123 456
//fn.bind(obj)(123, 456);//obj 123 456
10. class(es6新增)
类的创建
- class(类)是 ECMAScript 6 中新增的关键字,专门用于创建类的,类可被用于实现逻辑的封装。和之前的区别就是把原来公共属性的构造函数换成了类,类的本质是函数。
class Person {
// 静态属性
static language = '汉语';
static skin = 'yellow';
//静态方法,注意不要加function
static eat () {
console.log('吃');
}
// 实例成员
uname = '张三丰';
age = 23;
sex = '男';
hi () {
console.log('hi');
}
}
//实例化,调用类似,静态只能类名(构造函数)调用,实例只能实例对象调用
let obj = new Person();
console.log( obj.uname );
obj.say();
- constructor:是一个特殊的方法,如果我们不写,那么会自动的帮我们创建这个构造方法,consntructor在实例化对象(new)的时候会执行,通常constructor用于接受参数,做初始化操作,用来给不同的实例对象起不同的值。
class Person {
constructor (uname, age) {
this.uname = uname;//不要漏掉this
this.age = age;
}
say () {
console.log('说话');
}
}
let obj = new Person('张三丰', 22);
console.log(obj);
let obj1 = new Person('阿飞', 23);
console.log(obj1);
- 字面量创建对象里的成员是冒号,其它的要用等号
类的继承
extends:申明一个类为子类, super:调用父类的方法
class Father {
constructor (uname, age) {
this.uname = uname;
this.age = age;
}
qian () {
console.log('赚他一个亿');
}
}
// 继承(记得先写父,再写子):
// super:调用父类的方法
//如果相同,优先调用自己的
class Son extends Father {
constructor (uname, age, score) {
// 先调用super,再设定自己的成员
super(uname, age);
this.score = score;
}
qian () {
// 方法就近原则,
// 如果依旧想要调用父类里面的方法,用super
super.qian();
console.log('2mq');
}}
let obj = new Son('儿子', 2,99);
console.log( obj );//里面也有了father里面的属性
obj.qian();//赚他一个亿 2mq
11. 拷贝
- 简单的对象名赋值操作,传的是地址,如果想要不传地址,又能使得两个对象一样,就要用到拷贝。
浅拷贝
- 只拷贝简单的数据类型的,不能考虑成员是复杂数据类型的,复杂数据类型还是共有的,不常用
Object.assign(newObj, obj);
//等价于下述代码
// for ( let key in obj ) {
// newObj[key] = obj[key]
// }
深拷贝
全部拷贝,如果是复杂的数据类型,在进行遍历,直到全部是简单数据类型的赋值为止,运用递归的思想,代码固定,如下:
// 遍历
// 如果遇到obj[key]是复杂类型,再遍历操作
function kaobei (newObj, obj) {
for ( let key in obj ) {
if ( obj[key] instanceof Array ) {// obj[key] 是数组,不要误用typeof,因为复杂数据类型的typeof都是object
//上面那行代码也可以改写为if (obj[key].constructor == Array)
// 保证newObj[key]是数组
newObj[key] = [];
kaobei(newObj[key], obj[key]);
} else if ( obj[key] instanceof Object ) {// obj[key] 是对象
// 保证newObj[key]是对象
newObj[key] = {};
kaobei(newObj[key], obj[key])
} else {
newObj[key] = obj[key];
}
}
}
kaobei(newObj, obj);