javaScript3

sclweb / 2023-08-15 / 原文

事件

1.函数

函数主要用于封装代码块

函数封装的步骤:

1)总结复用代码,用function包装复用代码。

2)发现可变量,抽取为形参。---结合项目观察某些变量值,考量是否更改当前变量就可实现多途径使用。

3)形参尽量不要超过四个。

 

2.作用域

 

调试工具断点打的其实是:执行环境。--代码运行的时候才会产生执行环境。

执行环境导致作用域存在。所以只有代码(函数)执行的时候才会产生作用域。

一个函数就是一个 独立作用域,和this毫无关系。

this只有在函数或全局代码执行的时候才存在。

AO:activation Object 简称AO ---活动性对象,函数执行的时候才会产生,一但函数执行结束就【被销毁】

一个作用域中的变量,如果没有var,而直接赋值(只有着一种情况!),则层层作用域寻找,直到window,如果window也没有,则var一个

        一个作用域中的变量,如果没有var,直接参与运算,则层层作用域寻找,但是如果直到window都没有找到的话,就报错:xxx is not defined

关于函数嵌套:

 // 函数嵌套
{
// 通过调试工具可以看到,每一个函数执行的时候在scope(作用域)面板中生成了如下结构:
/* 
    {
      num1: 10,   //函数内部定义的变量
      this: window    // 作用域终点,JS先编译,再执行,
      编译后已经知道了当前这个作用域执行后的终点
    }

     我们将上面的对象称为:Activation Object 简称:AO —— 活动性对象,
     仅仅在函数调用的时候产生,一旦函数执行结束就被【销毁】
     既然是对象,就有办法保存下来
*/
  function a(){
    let num1=10;
    function b(){
      let num2 = 20
      num1++;
      console.log(num1);
      console.log(num2);

      function c(){
        let num3 = 20;
        console.log(num2);
      }
      c();
    }
    b();
  }
  a();
}

箭头函数中的【怪异现象】

/* 
    通过调试工具可以知道,函数执行的时候发生了:
    1. 首先收集全部的变量声明
    2. 为变量赋值
*/
function a(){
  let num = 10;
  const obj = {
    name:'张三',
    age:20
  }
  console.log(num)
  console.log(obj.age)
}
// 运行a可以看到效果(使用调试工具调试)
// a();


/*
    函数调用中的【怪异现象】:
    箭头函数在运行过程中,this的指向遵循于当前定义的作用域this终点;
    当箭头函数this终点指向window的时候,scope面板显示为undefined
*/
const obj = {
  name:'张三',
  fn:function(){
    let num = 1;
    console.log(this.name)

    let fn2 = ()=>{
      console.log(this)
    }
    fn2()
  },
  fn1:()=>{
    console.log(this.name)
  }
}
obj.fn();
// obj.fn1();

3.关于闭包

 // 跨作用于访问
 // 什么是闭包?
/* 
   闭包第一种形态:
   常规的跨作用域访问,执行结束后,所有AO都被销毁
*/

function a(){
  let num1 = 10;
       function b(){
         num1++;
         console.log(num1)
       }

       b()
     }

a()


function c(){
  let num=10;

  return function d(){
    num++;
    console.log(num)
    // console.log(this)       // script面板中展示的是closureFn,需要考证
  }

}

// 此处执行c函数,返回了d函数的代码片段给closure
let closureFn = c();

// 调用closure——使用调试工具观察scope面板
closureFn()

// closureFn()

// closureFn()

// closureFn()

closu = Object

function num(){
  return 1
}

闭包使用

/* 
  狭义的建立闭包:
  1. 子级作用域访问了父级作用域变量
  2. 子级作用域不随父级作用域销毁(实际父级的AO未被销毁)
  执行环境栈顺序:先入后出
*/
// 闭包的方式
function userManager(){
    var userList = [];
    var admin = '张三';
    var password = '12345';
    return {  //将函数对象抛出去了,外函数外面执行。又访问了外层变量。这就形成了闭包。
      				//当执行栈中执行环境依次销毁的时候,却找不到里层执行环境,而外层执行环境一直等待里面的销毁。
      				//这就导致外层作用域下的变量被保存下来。
        auth(pwd){
            if(pwd === password){
                console.log('欢迎回来')
            }
        },
        createUser(userName){
            userList.push(userName);
        } 
    }
}

const tools = userManager()

tools.auth('12345');

tools.createUser('李四')
tools.createUser('李四1')
tools.createUser('李四2')
tools.createUser('李四3')

 

4.面向对象编程

1)函数改写成对象的原则

1.尽量避免函数嵌套

2.如果有函数嵌套,提取后,一定要观察this的指向,及时修改this指向问题。

2)对象的优缺点

1.代码复用,灵活性强

2.数据不安全,任何人都可以访问对象内部方法。

 

 

5.组合寄生

// 组合寄生
// 原型链继承和寄生继承

function MyMenu(list){
  // call方法——改变this指向
  Menu.call(this,list)
}

// 原型链继承
// 1.声明一个空function,用这个function的prototype去接收父类的prototype
(function(){
  function A(){};
  A.prototype = Menu.prototype;
  // 2. prototype中有constrcutor属性,指向了Menu,需要改成MyMenu
  A.prototype.constructor = MyMenu;
  // 3. 使用MyMenu去接收A的prototype
  MyMenu.prototype = A.prototype;
})();

MyMenu.prototype.doClick = function(){
}

const myMenu = new MyMenu('.list1');
    

6.数组内置方 法

1)concat ---连接两个或更多数组,并返回新数组,不改变原数组

let arr1=[1,2,3]
let arr2=[4,5,6]
arr1.concat(7,8) //[1,2,3,7,8] //直接添加数据
arr1.concat(arr2) //[1,2,3,4,5,6] //连接数组,concat参数可传入多个数组

2)reverse ---反转数组 ,改变原数组

3)push ---向数组末尾添加一个或多个元素,改变原数组

4)unshift ---向数组头部插入一个或多个元素,改变原数组

5)pop ---删除数组最后一个元素,并返回删除的元素,改变原数组

6)shift ---删除数组第一个元素,并返回删除的元素,改变原数组

7)splice() ---增删改查都可以,第一个参数为开始的索引,第二个参数为删除的个数(可选),第三个参数为添加的元素(可选),第三个参数可以有多个。如果只给一个参数,默认删除给定索引以后全部的值。改变原数组

8)sort() --数组在原数组上进行排序,不生成副本。如果不给参数,默认按照数字升序---字母升序进行排列。一个数组中同时存在数字和字母,先数字。如果一个数组同时存在数字,字符串,数组,对象:顺序为数字-数组-对象-字符串)

//数字
***如果不给参数,sort会在每一项上调用String()转型函数,然后比较字符串来决定顺序。***
arr.sort(function(a,b){return b-a}) //数字降序排列   不给值,再反reverse转数组也可以实现
//字母
arr.sort() //字母升序a-z

9)forEach() ---数组每个元素都执行一次回调函数

注意:遍历基本数据类型值的时候,得用索引配合才能改变原数组值。

// 数组改值
let arr = [1,3,5,7,9];
arr.forEach(function(item){
  item = 30;
})
console.log(arr);   //输出 (5) [1, 3, 5, 7, 9]   

 // 数组改值
let arr = [1,3,5,7,9];
arr.forEach(function(item,index,arr){
  arr[index] = 30;
})
console.log(arr); //输出 (5) [30, 30, 30, 30, 30]

 

10)map() ---方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。不会检测空数组,不会改变原数组。

11)filter()---创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。参数为函数,不会检测空数组,不会改变原数组。

let ages=[18,19,14,20]
ages.filter(function(age){return age>=18})  //检测数组中大于等于18的 

12)every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供) --不会检测空数组,不会改变原数组。

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。
let ages=[18,19,14,20]
ages.every(function(age){return age>=18})  //false

13)some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。只要有一个满足条件就返回true,剩下的不再检测。全部不满足就返回false。--不会检测空数组,不会改变原数组。

let ages=[18,19,14,20]
ages.some(function(age){return age>=18})  //true

14)join() ---将数组拼接为字符串,省略参数时,默认为逗号隔开,不会改变原数组

15)indexOf() ---查找数组元素,和查找字符串一样,第一个参数要查找的元素,第二个为开始索引,找到返回索引,找不到返回-1

16)includes() ---查找是否包含指定元素,返回true或false ,也可以用于字符串。

let arr=[1,"aa",2]
arr.includes("aa") //true

17)flat() 多维数组转一维数组,参数为深度,默认1;

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

18)findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。当有元素满足,后面的不再检测。全不满足返回-1;---和indexOf差不多。 不会改变原始数组。

 

19)find() 返回传入一个测试条件(函数)符合条件的第一个元素值。全不满足返回undefined。

 

20)slice(start,end) ---提取一部分并生成新数组,两个参数,开始索引,结束索引(可选)。 只给一个参数,从开始截取到最后。 也可以用于字符串。

 如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。

例:一维数组按照固定长度拆分二维数组

split_array=(arr,len)=>{
	let newArr = [];
	for(let i=0;i<arr.length;i+=len){
		newArr.push(arr.slice(i,i+len));
	}
	return newArr;
}
let data = [1,2,3,4,5,6,7];
split_array(data, 3); //[[1,2,3],[4,5,6],[7]]

方式二:

function group(arr, len) {
  let index = 0;
  let newArray = [];
  while(index < arr.length) {
    newArray.push(arr.slice(index, index += len));
  }
  return newArray;
}

21)keys() ---遍历数组并拿到所有的索引生成一个新数组返回出来。用于对象的时候拿到所有的键名生成新数组返回出来。

let arr=[1,2,3,4]
console.log(Object.keys(arr)) // [0,1,2,3]
let obj={name:"张三",age:20}
console.log(Object.keys(obj)) //["name","age"] 

https://www.runoob.com/w3cnote/es6-array.html   ES6 数组

22)xxx.at(索引) --es2022新增

返回指定索引值

7.字符串内置方法

1)charAt() ---返回指定索引处的字符。如:str.charAt(1)

2)charCodeAt() --- 返回指定索引的字符的Unicode编码。

Unicode编码:Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

3)concat() ---连接两个或多个字符串,返回新的字符串。和数组使用方法一样。

4)includes() ---查找字符串中是否包含指定的子字符,返回true或false

5)IndexOf() ---查找 某个子字符在字符串中的位置,有返回索引,没有返回-1

6)replace() ---查找并替换符合的字符。第一个参数为正则匹配表达式,第二个参数为替换的字符。

let str="asdfba";
str.replace(/a/g,0) //"0sdfb0"

7)slice() ---拿到开始和结束的索引字符片段,并返回一个新的字符串。和数组用法一样。两个参数,开始和结束索引。

8)split() ---按照指定符分割成数组。

9)substr() --- 截取字符串,两参数,开始索引,截取个数(可选),如果省略第二个参数,默认截取到结尾。

10)substring() ---截取字符串,两参数,开始索引(包括),结束索引(不包括)。

11)toLowerCase() ---将全部字符转换成小写

12)toUpperCase() ---将全部字符转换成大写

13)trim() ---去除字符串两边的空白。jquery方法

$.trim(str)

14) replaceAll() ---替换所有匹配字符

15) match() -- 可在字符串内检索指定值,或找到一个或多个正则表达式的匹配。

16)xxx.at(索引) --es2022新增

17)startsWith() 方法 -- startsWith() 方法用于检测字符串是否以指定的前缀开始。

18)endWith() 方法 --endsWith() 方法用来判断当前字符串是否是以指定的子字符串结尾的(区分大小写)。

8.对象内置方法

1)toLocalString() ---转换时间为字符串可用

例:new Date(time).toLocalString() //将传入的时间转换为YY/MM/DD PM hh:mm:ss的格式

2)Object assign() --浅拷贝

3)Object.entries()

返回索引/值对应的迭代器

const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
}

 

9.uni限制input只能输入数字

<input type="number" v-model="sum" @input="click" />
  export default {
data() {
  return {
    sum: 0,
  }
},
  methods: {
    click(e){
      const v = e.detail.value
      this.sum = '1'

      const zero = /^(0{2,})|[^0-9]/g
      let final = 0
      if (!v) {
        final = 0
      } else {
        final = v.toString().replace(zero, (v) => {
          return 0
        })

        if (final.split('')[0] * 1 === 0) {
          final = final.slice(1) - 0 || 0
        }
      }
      this.$nextTick(() => {
        this.sum = final.toString() || '0'
      })
    },
  }
}

10.处理时间

var date = new Date();

date .getYear(); //获取当前年份(2位)

date .getFullYear(); //获取完整的年份(4位)

date .getMonth(); //获取当前月份(0-11,0代表1月)

date .getDate(); //获取当前日(1-31)

date .getDay(); //获取当前星期X(0-6,0代表星期天)

date .getTime(); //获取当前时间(从1970.1.1开始的毫秒数)

date .getHours(); //获取当前小时数(0-23)

date .getMinutes(); //获取当前分钟数(0-59)

date .getSeconds(); //获取当前秒数(0-59)

date .getMilliseconds(); //获取当前毫秒数(0-999)

date .toLocaleDateString(); //获取当前日期

var mytime=date .toLocaleTimeString(); //获取当前时间

date .toLocaleString( ); //获取日期与时间

//时间戳转日期
new Date(时间戳).toLocaleDateString()
//计算近多少(7)天
new Date(start.getTime() - 3600 * 1000 * 24 * 7).toLocaleDateString(),

 

11. Array.from()

1)将类数组转换为真正的数组

let arrayLike = {
    0: 'tom', 
    1: '65',
    2: '男',
    3: ['jane','john','Mary'],
    'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]

要将一个类数组对象转换为一个真正的数组,必须具备以下条件:
1、该类数组对象必须具有length属性,用于指定数组的长度。如果没有length属性,那么转换后的数组是一个空数组。
2、该类数组对象的属性名必须为数值型或字符串型的数字
ps: 该类数组对象的属性名可以加引号,也可以不加引号

2)将Set结构的数据转换为真正的数组

let arr = [12,45,97,9797,564,134,45642]
let set = new Set(arr)
console.log(Array.from(set))  // [ 12, 45, 97, 9797, 564, 134, 45642 ]

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。如下:
let arr = [12,45,97,9797,564,134,45642]
let set = new Set(arr)
console.log(Array.from(set, item => item + 1)) // [ 13, 46, 98, 9798, 565, 135, 45643 ]

3)将字符串转换为数组:

let  str = 'hello world!';
console.log(Array.from(str)) // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "!"]

4)Array.from

第一个参数可以是任何可迭代结构,或者有一个length属性和可索引元素的结构。

console.log(Array.from([12,45,47,56,213,4654,154])) //浅拷贝

12.可选链运算符

可选链 ?. 语法有三种形式:

obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined。可以连用。

obj?.prop1?.prop2

obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined。

obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined。

 

13.字符串转换数字=+

a=+b //b只能为字符串数字才有效

 

14.对象动态添加key值

例:一个数组对象中的某一个值当key 某一个当值
this.$store.state.course.forEach((item)=>{
  Object.assign(this.attrdate,{[item.attrId]:item.attrName})
})

 

15.正则对象 new RegExp

/\//g

replaceAll("/")

new Reg("/", ''g

//判断跳转来源是否缓存页面
beforeRouteEnter(to, from, next) {
  const caches = [
    '/order/business/detail',
    '/patient/patientTube/detail/roominfo',
    '/patient/patientTube/detail/recordInfo',
    '/business/order/detail',
    '/patient/mine/detail/roominfo',
    '/patient/mine/detail/recordInfo',
  ]
  const cache = caches.find(item => {
    return new RegExp(item).test(from.path) //查询满足条件的值
  })
  if (cache) {
    next()
  } else {
    next(vm => {
      vm.init()
    })
  }
},

16.选中与否判断

当点击时,传入的index和当前的请求参数index相同时候表示取消,否则选中

selectDate(index) {
  const type = this.queryType === index ? null : index
  this.queryType = type
  this.fetchDiagList({ type })
  //计算近期时间,赋值时间选择器
  if (type == null) {
    this.date = []
    return
  }
  //顺便匹配时间,根据数组下标或者对象key来匹配,抛弃switch和if...else
  const timeArr = [
    [this.getNearTime(0), this.getNearTime(0)],
    [this.getNearTime(3), this.getNearTime(0)],
    [this.getNearTime(7), this.getNearTime(0)],
  ]
  this.date = timeArr[index]
},

17.new Map()与new Array()

Map结构提供了“值—值”的对应,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

const getmap = new Map([['aa',1],['bb',2]]);
getmap.set('aa',2);
console.log(getmap.get('aa')); //2
console.log(getmap.has('aa')); //是否存在aa
const map = Map.from(getmap);//深度拷贝map对象
map.set('aa',1);
console.log(map.get('aa'));//1
console.log(getmap.get('aa'));//2
console.log(getmap.delete('aa'));//删除aa这个元素

map与array转换
const arr = Array.from(getmap);
console.log(arr)//[['bb',2]]
console.log(arr.flat());//['bb',2]

18.将 Object 转化为 Map

new Map() 构造函数接受一个可迭代的 entries 。 借助 Object.entries 方法你可以很容易的将 Object 转换为 Map:

const obj2 = { foo: 'bar', baz: 'abc' }; 
console.log(Object.entries(obj2));  // [['foo', 'bar'], ['baz', 'abc']]
const map = new Map(Object.entries(obj2)); 
console.log(map); // Map {'foo' => 'bar', 'baz' => 'abc'}

 

19.scrollIntoView导致的页面偏移解决

解决方案:放弃使用scrollIntoView 改用scrollTop 来操作滚动条。

 

el-scrollbar的滚动元素为this.$refs.scrollbar.wrap

//scrollIntoView方式 
handleTabClick({ name }) {
  const el = this.$refs.tabs.$el.querySelector(`#${name}`)
  el && el.scrollIntoView({ behavior: 'smooth' })
  this.activeName = name
},

//target目标元素 --锚点跳转
var target = document.getElementById("target");
target.parentNode.scrollTop = target.offsetTop;

offsetTop:元素上外边框距离父元素上内边框的距离(简单来说就是元素相对父元素上边的距离)

//scrollTo方式
const scroll = this.$refs['prescription-item'][index].$el.offsetTop
this.$refs['prescription-content'].scrollTo({
  top: scroll,
  behavior: 'smooth',
})

20.Infinity

Infinity 属性用于存放表示正无穷大的数值。

 

21.三目运用

item.medicalType === 'MZ'
  ? '门诊'
: item.medicalType === 'ZY'
  ? '住院'
: item.medicalType === 'TJ'
  ? '体检'
: ''

 

22.空值合并操作符??符

只有当左侧为null和undefined时,才会返回右侧的数

?? 这个和 || 很像,但是它不会屏蔽掉false和 0 和 ""
null ?? 1   ----1
undefined ?? 1   ----1
false ?? 1    ----false
0 ?? 1   ----0
"" ?? 1   ----""

 

23.尽量避免条件判断

写 if-else 不外乎两种场景:异常逻辑处理和不同状态处理。异常逻辑处理说明只能一个分支是正常流程,而不同状态处理都所有分支都是正常流程。

原则:

合并条件表达式可以有效地减少if语句数目;

减少嵌套能减少深层次逻辑;

异常条件先退出自然而然主干流程就是正常流程。

针对状态处理型重构方法有两种:

一种是把不同状态的操作封装成函数,简短 if-else 内代码行数;

另一种是利用面向对象多态特性直接干掉了条件判断。

 

24.Object.defineProperties()

Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
});

 

25.判断对象内是否包含某个键名

1) xxx in obj

'name' in {name:'jeck'} //true 
//缺点:如果属性来自对象的原型,也会返回true

2) Reflect.has(obj,xxx)

Reflect.has({name:'jeck'},'name') // true
//与xxx in obj 类似,缺点同理,Reflect是 ES6 为了操作对象而提供的新 API。所有属性和方法都是内置的。

3)hasOwnProperty()

var obj = {name:'jeck'}
obj.hasOwnProperty('name')

//缺点:不能检索Object.create(null) 创建的对象

 

4)Object.prototype.hasOwnProperty()

var obj = {name:'jeck'}
Object.prototype.hasOwnProperty.call(obj,'name') //true
//解决了方法3)的缺点 ,直接访问的对象的内置方法,跳过了原型连

5)Object.hasOwn() --es2022新方法(推荐使用)

var obj = {name:'jeck'}
Object.hasOwn(obj,'name') //true