js高级语法
前置话题
代码观看地址
语雀预习资料
思维导图
箭头函数
普通函数使用
箭头函数使用
语法:let test = (参数) => {函数体}
// 箭头函数
let func3 = ()=>{
console.log('3');
}
func3()
const的说明
说明:
1、const 声明的变量永远不能被修改,也称为常量
2、规范程序员设计,需要明确哪些变量可以修改,哪些不能修改
3、什么时候用const
1 多看老师的使用
2 担心 先使用 let
4、有经验的使用:能使用const就不用let
const username = 'dwjia'
username= 'csc'//报错
箭头函数传参
箭头函数 传参
如果只传递一个参数的话 小括号可以省略 :const func1 a =>{}
如果不传递参数 或者 传递参数大于1个的话,小括号不能省略
const func2 = a=>{
console.log('a');
}//只有一个参数,可以省略不写()
const fun3 = (a,b)=>{
console.log(a,b);
}//两个参数,必须写()
const fun4 = ()=>{
console.log('我是不传参数');
}//不传参,必须写
func2('1')
fun4('2')
fun3('3','4')
箭头函数返回值
返回值个数
只有一个返回值:可以省略{},相当于省略了retuen,只写后面的语句
const fn = (a)=>a+1 //只有一个返回值,可以省略{},这句相当于是return a+1
let num = fn(4)
console.log(num);
多个返回值:多个返回值则不能省略{}
const fn2 = (a)=>{
let b =a + 1;
let c = a + 2;
return {b,c}
} //多个返回值则不能省略{}
console.log(fn2(12));
返回值对象
// 返回数值
const fn1 = ()=> 123
// 返回字符串
const fn2 = () => '字符串'
// 返回布尔类型
const fn3 = () => true
// 返回会数组
const fn4 = () => ['a','sca']
// 返回对象
const fn5 = () => {user:'123'}
打印效果:
原因:
// 让箭头函数 你猜猜 我大括号是什么意思
// 1 猜 对象的 符号
// 2 猜 函数体结构 符号 (被识别为函数体,而不是对象)
解决方法:
如果你的箭头函数 省略大括号的 情境下
想要返回 一个对象 固定语法 添加一个小括号
const fn6 = ()=>({user:'23'})
console.log('fn6(): ', fn6());
结果:
函数参数默认值
const fn1 = (msg)=>{
console.log(msg);
}
fn1('没问题') //传参
fn1()//不传参
效果:
需求更新:返回一个数组的长度,如果没有传入数组,返回长度为0
const getLength = (arr = []) => console.log(arr.length);
let arr1 = ['1', '2']
getLength(arr1)
getLength()
效果:
解构
解构数组
let arr = ['0','1','2']
//普通区数组元素的方法
const str1 = arr[0]
const str1 = arr[1]
console.log(str1,str2);
//解构写法:只是一种更加方便获取数据的写法
//数组解构关注顺序
const [str1,str2] = arr //注意:这里获取数组使用[]
console.log(str1,str2);
变量交换
//解构的应用场景
//数字交换
let a = 100;
let b = 200;
[b,a] = [a,b];
console.log(a,b);
解构对象
// 对象解构
const obj = {
user:'我是鲁鲁修',
country:'大不列颠'
};
const {user,country} = obj; //注意:这里获取对象使用{}
console.log(user,country);
注意事项:
-
取值名与对象属性名保持一致,否则无法识别
const obj = { user:'我是鲁鲁修', country:'大不列颠' }; const {a,b} = obj; console.log(a,b);//undefined undefined
-
解构的符号要与类型一致
//解构数组arr const [str1,str2] = arr //注意使用[],使用数值和使用数组的都是[] //解构对象obj const {user,country} = obj; //注意使用{}
对象简写
对象属性简写
原理还是赋值,只是写法更加简便了,所以属性要与变量保持一致
const username = 'cc';
const skill = 'longLife'
const obj= {
username,//相当于username : username
skill, //skill: skill
// 书写一个没有定义的变量
// height//执行报错:height is not defined
}
console.log(obj);
对象函数简写
主要是省略了:function
const username='cc'
const obj = {
username,
// 以往写法:
// say:function(){}
//简写
say(){
console.log('方法');
}
}
console.log(obj);
剩余运算符
数组筛选
const arr = ['a','b','c','d','e']
const[let1,let2,...list] = arr;
console.log(list);
效果:
对象筛选
//对象筛选
const obj = {
a:1,
b:2,
c:3,
d:4
}
const{a,b,c,...obj2} = obj
console.log(obj2);//d:4
在函数的运用
//剩余运算符再函数中的运用
//传入多个参数时
calc()
calc(1)
calc(1,2)
calc(1,2,3)
function calc(...params){
//params可以获取到所有 传递给calc的参数 封装到一个数组中
console.log(params);//输出稳定,都是输出数组
}
复制引用类型
就是复杂数据类型
// 由于复杂数据类型的复制时按地址传输,因此更改新旧对象都会引起两者的改变,因此引入了剩余运算符的操作
//复制对象,双方改变不影响
const obj={kl:546,jn:594,cf:7878,ser:4844}
const newObj = {...obj}
obj.kl = '我改的'
console.log(newObj); //{kl:546,jn:594,cf:7878,ser:4844}
//复制数组
const arr = ['a','csc','gbg']
const newArr = [...arr]
arr.push('我增加的')
console.log(newArr);//['a','csc','gbg']
数组方法
map
const arr = ['a','b','c','d','e','f']
//需求:将数组的值放入每一个li标签中
// 普通做法
const newLi = []
arr.forEach(function(value){
newLi.push(`<li>${value}</li>`)
})
console.log(newLi);
//map做法
const newLi1 = arr.map(function(value,index){
// return `<li>${index}-${value}</li>`
return `<li>${value}</li>`
})
//简写做法
//const newLi1 = arr.map((value,index)=>`<li>${value}</li>`)
console.log(newLi1);
filter
const arr1 = [1,2,3,4,5,6]
const newArr0 = arr1.filter((value)=>{
return value>1
})
const newArr1 = arr1.filter((value)=>value>1)
console.log('newArr1:',newArr1);
every
类似于&&,全符合为真(检测一个不合格就弹出false,后面不再检测)
const names =['黄圣飞',
'子聪',
'王锦双',]
const result = names.every((v)=>v.length==3)
console.log(result);//true
注意:every对于空数组 也会返回true
const arr=[]
const result2 = arr.every((v)=>v.length==3)
console.log('result2: ', result2);//true
some
一个符合就返回true(一旦发现符合的,就返回true,后面的不再查看)
const arr =['健康',
'健康',
'健康',
'健康',
'不健康']
const result = arr.some((value)=>value==='不健康')
if(result){
console.log('有危险');
}else{
console.log('没事');
}
findindex
语法:arr.findIndex((value) => {条件})
let index = arr.findIndex((value) => value.height === 60)
// let index2 = arr.findindex(function(value,index){
// value.height===60
// })
console.log(index);
arr.splice(index, 1)
console.log(arr)
includes
语法:arr.includes(想要检测的元素)
let arr = ['a', 'b', 'v'];
//let result = arr.includes(value => value === 'a')//错误写法,includes语法错误
let result = arr.includes('a')//里面直接写条件
console.log(result)//true
indexOf
没有返回-1
const arr = ['a', 'c', 'd']
const index = arr.indexOf('a')
console.log(index)
-
类似于 findindex
- findIndex可以用于复杂数据类型,比如数组里面的对象等,
-
indexOf可以用于简单的数组
-
需求决定使用的方法
join
const arr = ['a', 'd', 'c', 'd']
const arrJoin = arr.join('-')
console.log(arrJoin)
// 把数组渲染成一个标签, 使用的方法主要是map和join
// const arr2 = ['<li>76</li>', '<li>5</li>', '<li>vf</li>', '<li>vv</li>']
// 拼接字符串
// arr = arr.map(value => `<li>${value}</li>`) //用map处理,这里报错了,说arr设置常量不能被更改
let newArr = arr.map(value => `<li>${value}</li>`)
newArr = newArr.join('')
console.log(newArr)
常用:
- 将数组渲染成标签,需要配合map和join实现
- join比较常见的用法就是直接链接空
set对象
使用
- set是一个对象,且里面的数据不允许重复出现
- 向对象里面增加数据,需要调用add方法
let set = new Set();
console.log(typeof set);
// 2、存放数据 调用add方法
set.add(1);
set.add(2);
set.add(3);
set.add(3); //重复数据不添加
set.add(4);
console.log(set);
效果:
- 将set对象转成数组
// 想要把set的数据遍历,使用forEach等等方法
// 将set对象转成数组
let arr = [...set]// 解构
console.log(arr)
效果:
- 将数组转成set对象
// 将数组转化为对象 直接将数组塞进去set对象就好
const arrset = [45, 78, 6, 77, 88]
let set2 = new Set(arrset)
作用
- 根据他的特性,可以用于去重
- 定义数组
- 将数组转化为set对象
- 再添加数据
面向对象
字面量
- 优点:清楚直观,适合于小demo
- 缺点:没有任何继承或着封装的特性,不利于后期维护,在大项目中不推荐使用
// 创建对象的方式一:字面量=》字面意思
// 缺点: 不方便维护 - 修改
const obj = {
name: '八戒', //如果现在需要把name->nickname,则需要将下面3哥也全部改变,可能会出现改多改少出现程序错误的现象
height: 160
}
const obj1 = {
name: '八戒',
height: 160
}
const obj2 = {
name: '八戒',
height: 160
}
const obj3 = {
name: '八戒',
height: 160
}
工厂函数
- 作用:实现了封装
// 创建对象的方式二:工厂函数
// 接收两个参数值,再从函数中返回一个数组
// 优点:便于维护,此时要修改属性名时只需要修改工厂函数属性的一个即可
function person(name, height) {
return {
nickname: name,
height //简写,全称是:height:height
}
}
const obj4 = person('cac', 445)
const obj5 = person('ccs', 455)
const obj6 = person('vfv', 45)
const obj7 = person('tery', 745)
console.log(obj4, obj5, obj6, obj7)
- 缺点:没有继承性
构造函数
- 本质:就是一个函数
- 作用:创建对象
- 只要是被new出来的对象,统称为实例
- 细节:
- 首字母大写
- 这样和内置的构造函数命名就统一了
let per = new Person(); //per是实例,1、先实例化对象
function Person() {
}//2、再使用
console.log(per)//返回空对象
效果:
// 非构造函数的写法( 普通函数)
let person = Person()
function Person() {}
console.log(person) //undefined
构造函数和this
- 每一个构造函数中 都存在 有一个 魔鬼 this
- 构造函数 默认情况下 就是返回了 this
- ⭐this 等于你所new出来的实例 per
- 只要给构造函数中的this 添加 属性或者方法,实例就能相对应的增加属性或方法
// 知识点1:
let set = new Set()
console.log(set) //这个内置的有好多属性,如果自定义的需要则自己添加
let per = new Person();
function Person() {
this.name = '秦始皇'
this.add = () => {}
//add = function(){}
this.clear = () => {}
}
console.log(per)
set内置对象:
Person对象:!
// 知识点2:
const pers = new Person();
function Person() {}
pers.username = 'baba'
// 现在单独开一个
const per2 = new Person()
console.log(pers)
console.log(per2) //不会做出更改
效果:
构造函数弊端
- 我们自己的构造函数,方法无法共享
- 指向堆的不同地址,如下一节所示
function Person() {
this.say = function () {};
}
const p1 = new Person();
const p2 = new Person();
console.log(p1 === p2); // 两个对象的比较 内存地址 false
console.log(p1.say === p2.say);// false 两个函数 存放在不同地址中
- 自带的的构造函数,方法共享
- 指向堆的同一个地址,如下一节所示
const s1 = new Set();
const s2 = new Set();
console.log(s1 === s2);// false
console.log(s1.add === s2.add); // true
引用类型在队中存放数据
构造函数方法指向同一个
解决方案:
- 在构造函数中 当我们定义方法的时候 一般都不会只在 构造函数中写死
- 让我们方法 都指向外部 单独声明的方法 多个实例去共享方法!!!
function say() {
console.log('我是外面的');
}
function Person() {
this.say = say //我把地址指向了外面的
}
const p1 = new Person()
const p2 = new Person()
console.log(p1 === p2);//false
console.log(p1.say === p2.say); //true
//指向外面后p1、p2的say都是指向同一个堆地址
总结:
1 值类型 是存在 栈空间 适合存放 固定大小的数据
2 引用类型(对象、数组、函数) 存在 堆空间 适合存放大小不固定的数据
3 new了两个对象的时候, 两个对象的内存地址 不相等
4 我们希望 两个对象的方法 内存地址是相等!!!
1 在构造函数中 当我们定义方法的时候 一般都不会只在在 构造函数中写死
让我们方法 都指向外部 单独声明的方法 多个实例去共享方法!!!
构造函数的基本使用
- 构造函数的时候 属性 分成了两种
1 函数类型的属性
一般都是写在外部!!!
2 非函数类型的属性
一般是写在内部!!!
const per = new Person(100);
function Person(hairNum) {
this.hairNum = hairNum;
this.decrease = decrease;//为了可以共享,我们把函数类型的属性卸载了外面,然后使用“=”链接路径
}
//这个就是写在外面的函数
function decrease() {
this.hairNum = this.hairNum + 10;
console.log('头发现存数量:', this.hairNum)
}
let button = document.querySelector('button')
button.addEventListener('click', () => {
per.decrease() //只需要每一次带点击实现增加即可
})
- 优点:对象构造时都可以使用这个函数
- 缺点:由于函数名只能使用一次,会造成全局污染,你用过之后别人就不能再使用,甚至会出现覆盖问题
构造函数+原型
-
使用背景:为了解决上面构造函数可能带来的全局污染的问题提出的结局方案
-
什么是原型:
-
原型有什么:
内置的函数
使用:
在外部【new的对象名】.【prototype】.【要添加的对象属性函数】= function(){}
function Person(name, height) {
this.name = name;
this.height = height
// this.say = say//不像构造函数一样直接写在里面做链接,这里面只放非函数属性
}
//单独写在外面
Person.prototype.say = () => {
console.log('我是写在原型里的', this.name);
}
const per = new Person('黑白棋', 158)
console.log(per);
per.say()
再次了解prototype
-
新语法:
console.dir();
-
作用:console.dir()方法可以在控制台显示指定javascript对象的属性。打印出该对象的所有属性和属性值.
-
话题引入:获取标签对象
const div = document.querySelector('div')
const button = document.querySelector('button')
console.dir(div);
console.dir(button);
效果:
再点开这个原型:
- HTMLDivElement
- HTMLButtonElement
共同点:他们都公用一个父类的原型
因此,我们也想做到父类的方法,我们子类要继承父类的方法,让他们有联系起来
因此以下提出几种方案:
原型来实现方法的继承
继承方法
使用方法:
- 子类的写在原型上的函数 = 父类原型上的函数
WhitePerson.prototype.say = Person.prototype.say
- 白人用了Person继承了say方法
// 父亲 Person ;say
// 儿子:
// YellowPerson
// WhitePerson
//老爹的方法
function Person(){}
Person.prototype.say = function(){
console.log('我是老爹的');
}
// 儿子1
function YellowPerson(){}
// 继承老爹的方法
YellowPerson.prototype.say = Person.prototype.say
YellowPerson.prototype.jump = ()=>{
console.log('我是儿子1自己的方法');
}
// 儿子2
function WhitePerson(){}
//我也要用老爹的方法
WhitePerson.prototype.say = Person.prototype.say
// new一个
const yp = new YellowPerson()
const wp = new WhitePerson()
console.log(yp);
console.log(wp);
yp.say();
wp.say();
// 添加儿子自己的方法还是原来的方式添加即可
yp.jump()
console.log(yp)//第一个儿子
第一个儿子:
继承属性
-
用什么继承:call的使用
-
怎么继承:属性的继承 在 儿子的构造函数中 调用 父亲的构造函数
-
使用方法:
父亲的构造函数.call(this,参数。。。)
//普通函数作为父亲
function Person(name,color,height,work){
this.name = name
this.color = color
this.height = height
this.work = work
}
//构造函数继承父元素的属性
function YellowPerson(name,color,height,work,nickname){
Person.call(this,name,color,height,work);
this.nickname = nickname;
}
const person = new Person()
const yp = new YellowPerson('chjds','defew','565465','wwee','fefee')
console.log(yp);//打印结果如下所示
效果:
封装于继承案例
案例 : 目的 利用 面向对象的语法和思想 来结合 dom操作 解决实际的问题
需求:
1 定义父构造函数
HTMLElement
1 属性 dom
2 方法 appendTo
2 定义 一个儿子 HTMLDivElement
1 会继承 父亲 属性和方法
2 儿子还有自己独立的功能
const myDiv = new HTMLDivElement("div中的文本")
myDiv.appenTo("p") ; p标签中会多出有一个 文本为 “div中的文本” 的div
儿子还会有自己的一个方法
点击按钮 myDiv.scale(2); 缓慢放大两倍
3 定义 另外一个儿子 HTMLImgElement
1 会继承 父亲 属性和方法
2 自己独立的功能
const myImg = new HTMLImgElement(图片的地址)
myImg.appendTo("a") a标签中会多了一个 图片标签
儿子还会有自己的一个方法
点击某个按钮 myImg.borderRadius("50%") 缓慢变成 边框圆角 50% 效果
4 代码提示
1 先根据需求 把 代码的解构整理好
2 再去思考实现的逻辑
*/
静态结构:
<button>div放大两倍</button>
<input type="button" value="设置图片缓慢边框圆角效果" />
<div class="box"></div>
类的基本使用
es5
-
1 构造函数 来创建对象
-
2 对象 两种信息
- 1 属性
- 2 方法
es6
- 优点:es6 新 简洁 class 面向对象
初级实现代码:
一点拓展:js中的原型(用来解决下面普通函数为什么有prototype的问题)
es5:
function Person(){
//增加属性需要使用到this
this.name = '13';
this.height = 150;
}
//可以看出,书写对象的方法需要单独拿出来,写在原型里面
Person.prototype.say(){
console.log('我是大美铝')
}
const p2 = new Person();
es6:
class Person{
name= '123';
height = 150;
say(){
console.log('我是大美铝');
}//写在了原型里面
}
const person = new Person();
console.log(person);
- 效果:
class方式实现继承
- 核心语句:
extends、super
对于子类来说
1 如果你写了 extends 而且 还写 constructor
那么在我们的 constructor 必须要调用super 固定语法!!
class Person {
constructor(name,height){
this.name = name;
this.height = height;
}
fly(){
console.log('我飞');
}
}
class YellowPerson extends Person{
constructor(name,height,nickname){
super(name,height);// 调用父亲的构造器 给儿子 添加属性
this.nickname = nickname;
}
}
const person = new Person('123',150,"hh");
console.log(person);
2 为什么 之前只写 extends 不用写super-没有写 constructor
class Person {
name = '父亲';
say() {
console.log(this.name, '父亲的say方法');
}
}
// 直接就实现了 继承父亲的属性和方法 !!!!
class YellowPerson extends Person {}
// 儿子实例 拥有 父亲 name属性和父亲的 say方法
const yp = new YellowPerson();
console.log(yp);
- 效果:
检测数据类型
-
基本的数据类型 typeof
-
引用数据类 intanceof
-
检测 你是不是 你爸爸亲生!! 检测 这个实例是不是被某个构造函数 new 出来
class Person {}
class SuperPerson{}
const p1 = new Person();
const s1 = new SuperPerson();
console.log(p1 instanceof Person);// true
console.log(p1 instanceof SuperPerson); // false
this指向
全局函数的this的指向
-
常态:一般来说 this的指向 判断依据 谁调用了 函数 this 指向谁
-
其实 当我们定义了全局的函数的时候,默认是挂载到了window 全局变量上
-
全局函数 其实就是 window的一个属性而已 只不过 平时写代码的时候 可以省略这个window
//通常采用缩写形式
function abc(){
console.log(this);
}
abc()//window
//展开形式
window.abc = function(){
console.log(this);
}
window.abc()//window
小结
1 当我们定义全局的函数的时候 本质是给window添加了一个 属性(函数类型)
2 当我们调用这个函数 的时候 abc() 本质 window.abc() -- window 可以被省略而已
箭头函数没有this
- 箭头函数没有内部的this
- 当你的
修改箭头函数的指向
用于封装复杂代码,一般的业务用不到,但是面试常问
- bind
- call
- apply
原型继承
优点
缺点:
prototype是一个对象,是一个复杂数据类型,直接写等于的话,两个原型指向同一个地址,会出现:儿子改,父亲改变
原型链
-
只是一种代码的组织形式而已
-
可以通过原型对象prototype将多个对象关联起来 父子结构 看div的dom元素原型
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通