Web前端之零碎知识点、基础知识、字符串、数组

MENU


前言

每一个大标题,曾经都是一篇文章,此文章内容比较散乱。


web前端之delete和Vue.$delete删除数组的区别、empty、undefined

删除数组

1. delete只是把数组元素的值变成emptyundefined,元素的键不变,数组长度不变。
2. Vue.$delete直接删除数组,改变数组的键值和长度。


删除对象

两者相同,都会把键名(属性/字段)和键值删除。


共同特点

arr.forEach(item => {
	// if(item.id === 4) delete item;
	if(item.id === 4) this.delete(item);
});

两者都不能删除变量。


示例代码

let objectD = { name: "xiaojiayu", age: 24, gender: "man" };
let arrayD = [1, 2, 3, 4, 5];
delete objectD.age;
this.$delete(objectD, "name");
delete arrayD[3];
this.$delete(arrayD, 0);

console.log("objectD:", objectD);
console.log("arrayD:", arrayD);

结果

delete和vue.$delete


JavaScript实现挑选汽车的功能、构造函数、实例化、模板字符串、解构、this、params、new

1、代码实现

function Car(params) {
	this.brand = params.brand;
	this.color = params.color;
	this.displacement = params.displacement;
}

function Person({ name, age, carOpt }) {
	this.name = name;
	this.age = age;
	this.selectCar = function () {
		// 在Person构造函数中实例化Car构造函数
		let myCar = new Car(carOpt);
		return `${this.name}挑选了一辆排量为${myCar.displacement}${myCar.color}${myCar.brand}`;
	}
}

// 实例化Person构造函数
let jone = new Person({
		name: '半晨',
		age: 28,
		carOpt: {
			brand: '五菱宏光S',
			color: '奶白色',
			displacement: '1.5'
		}
});

2、调用Person构造函数里面的selectCar方法

console.log(jone.selectCar());
// 半晨挑选了一辆排量为1.5的奶白色五菱宏光S。

JavaScript之统计字符串、replace、undefined

描述

1、统计一个字符串中字符出现的次数。
2、获得次数最多的一个,共出现几次。
3、用字典的方式。


代码

function statistics(string) {
	// 利用正则去除所有空格
	string = string.replace(/\s+/g, '');
	
	// 定义字面量对象
	var dictionaries = {};
	for (var i = 0; i < string.length; i++) {
		// 如果 dictionaries 
		// 对象中不包含当前字母为属性名的成员
		if (dictionaries[string[i]] === undefined) {
			// 强行添加一个以当前字母为属性名的成员,
			// 初始值为 1
			dictionaries[string[i]] = 1;
		} else {
			// 否则字典中已有这个字母为属性名的成员,
			// 就为当前属性名的值 += 1
			dictionaries[string[i]] += 1;
		}
	}

	var max = null,
	count = 0;
	// 遍历对象中每个属性
	for (var key in dictionaries) {
		// 用当前属性值和 count 比较
		// 如果当前属性值大于 count 时,
		// 才取而代之,并将当前字符(属性),保存在 max 中
		if (dictionaries[key] > count) {
			max = key;
			count = dictionaries[key];
		}
	}

	return { dictionaries, max, count };
};

console.log(statistics('I miss you'));
// {dictionaries: {…}, max: "s", count: 2}
// dictionaries: { I: 1, i: 1, m: 1, o: 1, s: 2, u: 1, y: 1 }
// max: "s"
// count: 2

JavaScript实现月份累加,比如‘2021-01-31‘加一个月得到‘2021-02-28‘

主要代码

function addMonth(initialDate, months) {
	let date = new Date(initialDate),
		oldDate = date.getDate();
	date.setDate(1);
	date.setMonth(date.getMonth() + months);
	let newDay = new Date(date.getYear(), date.getMonth() + 1, 0).getDate();
	date.setDate(Math.min(oldDate, newDay));
	return date;
}

返回的是标准时间Wed Feb 17 2021 00:00:00 GMT+0800 (GMT+08:00),所以需要格式化处理。


时间格式化

function addMonth(initialDate, months) {
	let date = new Date(initialDate),
		oldDate = date.getDate();
	date.setDate(1);
	date.setMonth(date.getMonth() + months);
	let newDay = new Date(date.getYear(), date.getMonth() + 1, 0).getDate();
	date.setDate(Math.min(oldDate, newDay));
	console.log(date);
	// Wed Feb 17 2021 00:00:00 GMT+0800 (GMT+08:00)
	return parseTime(date, '{y}-{m}-{d}');
}

function parseTime(time, pattern) {
	if (arguments.length === 0 || !time) {
		return null;
	}
	const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
	let date;
	if (typeof time === 'object') {
		date = time;
	} else {
		if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
			time = parseInt(time);
		} else if (typeof time === 'string') {
			time = time.replace(new RegExp(/-/gm), '/');
		}
		if ((typeof time === 'number') && (time.toString().length === 10)) {
			time = time * 1000;
		}
		date = new Date(time);
	}
	const formatObj = {
		y: date.getFullYear(),
		m: date.getMonth() + 1,
		d: date.getDate(),
		h: date.getHours(),
		i: date.getMinutes(),
		s: date.getSeconds(),
		a: date.getDay()
	};
	const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
		let value = formatObj[key];
		if (key === 'a') {
			return ['日', '一', '二', '三', '四', '五', '六'][value];
		}
		if (result.length > 0 && value < 10) {
			value = '0' + value;
		}
			return value || 0;
	});
	return time_str;
}

console.log(addMonth('2021-1-17', 1));
// 2021-02-17

JavaScript之检测数据类型、typeof、instanceof、toString、call、constructor、isNaN、isArray

最常见的判断方法:typeof

注意:其中typeof返回的类型都是字符串形式。


console.log(typeof "hello world"); 
// => "string"     
console.log(typeof 'undefined'); 
// => "string"
console.log(typeof 123); 
// => "number"
console.log(typeof true); 
// => "boolean"
console.log(typeof undefined); 
// => "undefined"
console.log(typeof Symbol()); 
// => "symbol"
console.log(typeof null); 
// => "object"
console.log(typeof [1,2,3]); 
// => "object"
console.log(typeof new Date()); 
// => "object"
console.log(typeof new RegExp()); 
// => "object"
console.log(typeof new Function()); 
// => "function"

已知是对象类型:instanceof

注意instanceof后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。

console.log([1,2,3] instanceof Array); 
// => true
console.log(new Date() instanceof Date); 
// => true
console.log(new Function() instanceof Function); 
// => true
console.log(null instanceof Object); 
// => false

根据对象原型链检测:Object.prototype.toString.call()

1、适用于所有类型的判断检测,注意区分大小写.toString()方法,在Object原型上返回数据格式。
2、原生检测方法,不存在兼容性问题。
3、call()调用且改变this指向,因为大部分数据类型都有自己的toString()方法。
4、toString()可以输出一个对象的内部属性class,查看对象的类型名。

console.log(Object.prototype.toString.call("123")); 
// => [object String]
console.log(Object.prototype.toString.call(123)); 
// => [object Number]
console.log(Object.prototype.toString.call(true)); 
// => [object Boolean]
console.log(Object.prototype.toString.call(null)); 
// => [object Null]
console.log(Object.prototype.toString.call(undefined)); 
// => [object Undefined]
console.log(Object.prototype.toString.call(Symbol())); 
// => [object Symbol]
console.log(Object.prototype.toString.call([1, 2, 3])); 
// => [object Array]
console.log(Object.prototype.toString.call({name: 'Hello'})); 
// => [object Object]
console.log(Object.prototype.toString.call(function () {})); 
// => [object Function]
console.log(Object.prototype.toString.call(new Date())); 
// => [object Date]
console.log(Object.prototype.toString.call(/\d/)); 
// => [object RegExp]

根据对象的constructor进行检测

constructor判断方法跟instanceof相似,只是检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型。

注意

1、nullundefined没有constructor
2、判断数字时使用圆括号,比如(123).constructor,如果写成123.constructor会报错;
3、constructor在类继承时会出错,因为Object会被覆盖掉,检测结果就不对。

// 注意当出现继承的时候,使用constructor会出现问题
function A() {};
function B() {};
A.prototype = new B(); // A继承自B
console.log(A.constructor === B); 
// => false
var C = new A();
// 现在开始判断C是否跟A的构造器一样
console.log(C.constructor === B); 
// => true
console.log(C.constructor === A); 
// => false 
// 解决这种情况,通常是手动调整对象的constructor指向
// 将自己的类赋值给对象的constructor属性
C.constructor = A;
console.log(C.constructor === A); 
// => true
console.log(C.constructor === B); 
// => false

jQuery方法:jquery.type()/$.type()

据说是无敌万能的方法,如果对象是nullundefined,直接返回'null''undefined'
注意:在使用时,一定要引入jQuery文件,不然会报错,jQuery is not defined**


console.log(jQuery.type(undefined) === "undefined"); 
// => true
console.log(jQuery.type() === "undefined"); 
// => true
console.log(jQuery.type(window.notDefined) === "undefined"); 
// => true
console.log(jQuery.type(123) === "number"); 
// => true
console.log(jQuery.type('123') === "string"); 
// => true
console.log(jQuery.type([]) === "array"); 
// => true
console.log(jQuery.type(true) === "boolean"); 
// => true
console.log(jQuery.type(function(){}) === "function"); 
// => rue
console.log(jQuery.type(new Date()) === "date"); 
// => true
console.log(jQuery.type(/\d/) === "regexp"); 
// => true
console.log(jQuery.type(new Error()) === "error"); 
// => true jQuery 版本高于 1.9.3
console.log(jQuery.type({name:'Hello'}) === "object"); 
// => true
console.log(jQuery.type(Symbol()) === "symbol"); 
// => true

严格运算符(===)有局限

console.log(typeof null); 
// => 'object'
console.log(null === null); 
// => true
console.log(undefined === undefined); 
// => true

isNaN

console.log(NaN == NaN); 
// => false
console.log(isNaN(10)); 
// => false
console.log(isNaN('')); 
// => false
console.log(isNaN(false)); 
// => false
console.log(isNaN(true)); 
// => false

检测数组:isArray

// ES5新增语法
// 存在兼容性问题,比如,IE的低版本
console.log(Array.isArray({})); 
// => false
console.log(Array.isArray([])); 
// => true

以上方法需要了解面向对象、原型prototype、原型链

在这里插入图片描述


参考文章

原文地址


JavaScript获取数组中日期的最小值和最大值、生成两个日期的连续日期、比较日期时间的大小、map、toISOString、sort、split、setDate、getDate

获取最小和最大日期值

function dateTimeMinMax(arr = []) {
    let dateArr = arr.map(item => new Date(item)).sort((a, b) => a - b),
        minDate = dateArr[0],
        maxDate = dateArr[dateArr.length - 1];

    minDate = minDate.toISOString().split('T')[0];
    maxDate = maxDate.toISOString().split('T')[0];

    return {
        minDate,
        maxDate
    };
}

console.log(dateTimeMinMax(['2023-05-01', '2023-06-20', '2023-05-05', '2022-12-12', '2023-07-07', '2023-09-27']));
// {minDate: '2022-12-12', maxDate: '2023-09-27'}

生成两个日期的连续日期(包括两头)

function continuousDateTime(statusDate = new Date(), endDate = new Date()) {
    statusDate = new Date(statusDate);
    endDate = new Date(endDate);

    let arr = [],
        i = 1;

    while (statusDate <= endDate) {
        arr.push({
            id: `id_${i}`,
            dateTime: statusDate.toISOString().split('T')[0]
        });
        statusDate.setDate(statusDate.getDate() + 1);
        i++;
    }

    return arr;
}

console.log(continuousDateTime('2023-08-27', '2023-09-07'));
// [
//     { "id": "id_1", "dateTime": "2023-08-27" },
//     { "id": "id_2", "dateTime": "2023-08-28" },
//     { "id": "id_3", "dateTime": "2023-08-29" },
//     { "id": "id_4", "dateTime": "2023-08-30" },
//     { "id": "id_5", "dateTime": "2023-08-31" },
//     { "id": "id_6", "dateTime": "2023-09-01" },
//     { "id": "id_7", "dateTime": "2023-09-02" },
//     { "id": "id_8", "dateTime": "2023-09-03" },
//     { "id": "id_9", "dateTime": "2023-09-04" },
//     { "id": "id_10", "dateTime": "2023-09-05" },
//     { "id": "id_11", "dateTime": "2023-09-06" },
//     { "id": "id_12", "dateTime": "2023-09-07" }
// ]

getDate
MDN

根据本地时间,返回一个指定的日期对象为一个月中的哪一日(从1~31)。


w3school

getDate()方法返回指定日期在月中的第几天(从1到31)。


setDate
MDN

setDate()方法根据本地时间来指定一个日期对象的天数。


w3school

setDate()方法将月份中的某一天设置为日期对象。


split
w3school

split()方法将字符串拆分为子字符串数组。
split()方法返回新数组,不会更改原始字符串。
如果 (" ") 用作分隔符,则字符串在单词之间进行拆分。


MDN

split()方法接受一个模式,通过搜索模式将字符串分割成一个有序的子串列表,将这些子串放入一个数组,并返回该数组。


sort
w3school

sort()方法对数组的项目进行排序。
排序顺序可以是按字母或数字,也可以是升序(向上)或降序(向下)。
默认情况下,sort()方法将按字母和升序将值作为字符串进行排序。
这适用于字符串(Apple出现在Banana之前)。但是,如果数字按字符串排序,则25大于100,因为2大于1。
正因为如此,sort()方法在对数字进行排序时会产生不正确的结果。
您可以通过提供“比较函数”来解决此问题。
sort()方法会改变原始数组。


MDN

sort()方法就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的UTF-16码元值升序排序。
由于它取决于具体实现,因此无法保证排序的时间和空间复杂度。
如果想要不改变原数组的排序方法,可以使用toSorted()


toISOString
w3school

toISOString()方法使用ISO标准将Date对象转换为字符串。
该标准称为ISO-8601,格式为:YYYY-MM-DDTHH:mm:ss.sssZ


MDN

toISOString()方法返回一个ISO(ISO 8601 Extended Format)格式的字符串:YYYY-MM-DDTHH:mm:ss.sssZ。时区总是UTC(协调世界时),加一个后缀“Z”标识。


map
MDN

map()方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。


w3school

map()方法使用为每个数组元素调用函数的结果创建新数组。
map()方法按顺序为数组中的每个元素调用一次提供的函数。
注释:map()对没有值的数组元素不执行函数。
注释:map()不会改变原始数组。


JavaScript之粗浅原型和原型链

示例代码

function Student(params) {
	this.params = params;
}

let student = new Student('159357');

console.log(student.__proto__ === Student.prototype);
// true
console.log(student.constructor === Student);
// true
console.log(Student.prototype.constructor === Student);
// true

注解

1、实例的__proto__全等于函数的原型对象。
2、实例的构造函数全等于函数本身。
3、函数的原型对象的构造函数全等于函数本身。


JavaScript之循环特写、for、while、break

let i = 0;
for (; i < 7;) {
	console.log('for:', i);
	i++;
}
// 此处重新初始化i后while才能继续循环,
// 因为上一个for循环已经把i的值改为7啦
i = 0;
while (i < 7) {
	console.log('while:', i);
	i++;
}
// 此处重新初始化i后for才能循环执行,
// 因为i = 0;的结果为false,
// 所以for循环只会执行一次
i = 1;
for (; i;) {
	console.log('interrupt:', i);
	i++;
	// 中断条件
	// if (i == 8) break;
	// 使用return会报错,所以for循环中不能使用return
	// Uncaught SyntaxError: Illegal return statement
	// 未捕获的SyntaxError:非法返回语句
	// if (i == 8) return;
	// 当i = 0;时不再进入循环,
	// 也就是说for(; i;)条件不成立。
	if (i == 8) i = 0;
}

web前端之css多重边框、使用边框实现多环边框,类似多重环、outline、box-shadow

outline

background: #409EFF;
border: 10px solid #67C23A;
outline: 5px solid #E6A23C;

优点
1、比较灵活,可以自由设置边框的样式;
2、也可以通过outline-offset来设置跟元素边缘之间的距离,这个属性可以接受负值。设置负值时则是显示的内边框,类似于box-shadow的inset。


缺点
1、只适用于双边框的样式,多边框的样式不能用;
2、产生的边框不能贴合设置了圆角的元素,需要配合box-shadow来优化这个问题(后面会提到);
3、根据CSS基本UI特性(第三版)规范,描边可以不是矩形,使用时要测试各个浏览器的表现情况。


box-shadow

margin: 50px auto;
box-shadow: 0 0 0 10px #67C23A, 0 0 0 20px #F56C6C, 0 0 0 30px #E6A23C;

优点
1、相比较于outline,这种方式可以设置多重边框;
2、除了边框,box-shadow的偏移还能实现其他更多神奇的效果


缺点
1、相比较outline边框的自由设置,box-shadow不能实现类似边框虚线的效果;
2、不会影响布局,也不会受到box-sizing属性的影响;
3、不会影响鼠标的事件,比如hover还有click,使用的时候要注意。


相关链接

1、掘金-CSS学习:多重边框


JavaScript封装检测数据类型功能、Object、prototype、toString、call

function isType(type) {
    return function (obj) {
        return Object.prototype.toString.call(obj) === `[object ${type}]`;
    }
}

let isString = isType('String'),
    isNumber = isType('String'),
    isObject = isType('Object');

console.log(isString(1));
// false
console.log(isString('7'));
// true
console.log(isNumber(3));
// false
console.log(isObject({ id: 1 }));
// true

web前端之微信小程序实现复选连选功能、every、forEach、push、split

html部分

<view class="checkbox_box">
	<checkbox-group>
		<label class="check_box_item" wx:for="{{timeData}}" wx:key="index" bindtap="checkboxLabel" data-item="{{item}}" data-index="{{index}}">
			<view class="order_check_box">
				<view class="order">{{(index+1)<10?'0'+(index+1):index+1}}</view>

				<view class="check_box">
					<checkbox color="#0000EE" value="{{index}}" disabled="{{item.status == 0}}" checked="{{item.checks}}" />
				</view>
			</view>

			<view class="text">{{item.value}}</view>
		</label>
	</checkbox-group>

	<view class="submit_box">
		<view class="show">{{showSubmit}}</view>
		<view class="btn" bindtap="submit">保存</view>
	</view>
</view>

JavaScript部分

Page({
  /**
   * 页面的初始数据
   */
  data: {
    // 时间面板数据
    timeData: [{
        id: 1,
        status: 0,
        checks: false,
        value: '06:00-06:30'
      }, {
        id: 2,
        status: 0,
        checks: false,
        value: '07:00-07:30'
      }, {
        id: 3,
        status: 0,
        checks: false,
        value: '08:00-08:30'
      },
      {
        id: 4,
        status: 1,
        checks: false,
        value: '09:00-09:30'
      },
      {
        id: 5,
        status: 1,
        checks: false,
        value: '10:00-10:30'
      },
      {
        id: 6,
        status: 1,
        checks: false,
        value: '11:00-11:30'
      },
      {
        id: 7,
        status: 1,
        checks: false,
        value: '12:00-12:30'
      },
      {
        id: 8,
        status: 1,
        checks: false,
        value: '13:00-13:30'
      },
      {
        id: 9,
        status: 1,
        checks: false,
        value: '14:00-14:30'
      },
      {
        id: 10,
        status: 1,
        checks: false,
        value: '15:00-15:30'
      },
      {
        id: 11,
        status: 1,
        checks: false,
        value: '16:00-16:30'
      },
      {
        id: 12,
        status: 1,
        checks: false,
        value: '17:00-17:30'
      },
      {
        id: 13,
        status: 1,
        checks: false,
        value: '18:00-18:30'
      },
      {
        id: 14,
        status: 0,
        checks: false,
        value: '19:00-19:30'
      },
      {
        id: 15,
        status: 0,
        checks: false,
        value: '20:00-20:30'
      },
    ],

    minI: null,
    maxI: null,

    // 展示的数据
    showSubmit: '请选择时间'
  },

  // 收集复选框数据
  checkboxLabel({
    currentTarget
  }) {
    let {
      item,
      index
    } = currentTarget.dataset;
    let minI = this.data.minI;
    let maxI = this.data.maxI;
    let timeData = this.data.timeData;

    // 禁选
    if (timeData[index].status == 0) {
      return false
    }

    // 第一次点击的时候
    if (minI == null && maxI == null) {
      this.setData({
        minI: index,
        maxI: index,
        [`timeData[${index}].checks`]: !item.checks
      });

      return false;
    }

    if (index < minI) {
      this.setData({
        minI: index,
      });
    }

    if (index > minI && index < maxI) {
      this.setData({
        maxI: index - 1,
      });

      for (let i = this.data.maxI + 1; i < timeData.length; i++) {
        this.setData({
          [`timeData[${i}].checks`]: false
        })
      }
    }

    if (index > maxI) {
      this.setData({
        maxI: index,
      });
    }

    if (index == minI) {
      this.setData({
        minI: index + 1,
        [`timeData[${index}].checks`]: false
      })
    }

    if (index == maxI) {
      this.setData({
        maxI: index - 1,
        [`timeData[${index}].checks`]: false
      })
    }

    // 这里是给选中和连选的数据赋值
    // 也就是修改 data 中的原数据
    // 仔细看循环条件就不难理解了
    for (let i = this.data.minI; i <= this.data.maxI; i++) {
      this.setData({
        [`timeData[${i}].checks`]: true
      })
    }

    // 初始化
    // 这是在经过一波操作后,
    // 所有选框都是未选中状态后再次选择时
    if (timeData.every(_ => _.checks == false)) {
      this.setData({
        minI: null,
        maxI: null,
        showSubmit:'请选择时间'
      });
    }
  },

  // 保存
  submit() {
    let data = [];
    let showData = '';

    this.data.timeData.forEach(item => {
      if (item.checks) {
        data.push({
          id: item.id,
          value: item.value
        });
      }
    });

    if (data.length == 0) {
      showData = '请选择时间';
      wx.showToast({
        title: '请选择',
        icon: 'none',
        duration: 1500
      })
    } else if (data.length == 1) {
      showData = '您选择的是 ' + data[0].value + ' 时间段';
    } else {
      let startStr = data[0].value.split('-')[0];
      let endStr = data[data.length - 1].value.split('-')[1];
      showData = `您选择的是 ${startStr}-${endStr} 时间段`;
    }

    this.setData({
      showSubmit: showData
    });
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

style

.search_box {
  width: 100%;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.show_search {
  width: 360rpx;
  margin: 50rpx auto;
  box-shadow: 0 0 10rpx #888888;
  border-radius: 12rpx;
  line-height: 50rpx;
  font-size: 30rpx;
  box-sizing: border-box;
  padding: 10rpx;
}

.input_btn_box {
  display: flex;
  justify-content: center;
  align-items: center;
}

.input_text {
  border: 1px solid #888888;
  width: 300rpx;
  height: 50rpx;
  font-size: 30rpx;
  border-radius: 10rpx;
  padding: 0 16rpx;
  box-sizing: border-box;
}

.img_text_box {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: 20rpx;
  box-shadow: 0 0 10rpx #666666;
  border-radius: 10rpx;
  background-color: #9FB6CD;
  padding: 16rpx;
  box-sizing: border-box;
}

.img_text_box>image {
  width: 36rpx;
  height: 30rpx;
}

.img_text_box>view {
  color: #FFFFFF;
  margin-left: 10rpx;
  font-size: 26rpx;
  font-weight: 600;
}

web前端之微信小程序模拟生成验证码、switch、floor、random

1、html 部分

<view class="fixed_50_5">
	<view class="dis_r_se">
		<view class="width_130 line_height_70 text_align_center shadow_0616_666 radius_6" wx:for="{{numberI}}" wx:key="index" catchtap="tabBar" data-item="{{item}}">{{item}}</view>
	</view>

	<view class="margin_tb_36 text_align_center">当前验证位数:{{codeNum}}</view>

	<view>
		<radio-group bindchange="radioChange">
			<view class="dis_r_c">
				<view class="grid_c2_310 grid_column_gap_30 grid_row_gap_30">
					<label class="dis_r_fs" wx:for="{{codeType}}" wx:key="index">
						<radio value="{{item.value}}" checked="{{item.id==1?true:false}}" />
						<view style="margin-left: 20rpx;">{{item.title}}</view>
					</label>
				</view>
			</view>
		</radio-group>
	</view>

	<view class="dis_r_se margin_tb_36">
		<view class="width_260 height_80 line_height_80 text_align_center shadow_0616_666 font_36">{{code}}</view>
		<button class="back_theme" style="width: 200rpx; margin: 0; padding: 20rpx 0;" catchtap="verificationCode">生成验证码</button>
	</view>

	<view class="dis_r_sa" wx:if="{{code}}">
		<input class="border_888 padding_lr_20 radius_6 height_70" value="{{inputVal}}" placeholder="请输入验证码" bindinput="inputF"></input>
		<view class="font_26 color_red">区分大小写</view>
		<view class="back_tan radius_6 pading_16" catchtap="checkCode">校验</view>
	</view>
</view>

JavaScript 部分

const {
  globalData: {
    jsonData: {
      numberI,
      numbers,
      lowercaseLetters,
      capital,
      codeType
    },
    showToast
  }
} = getApp();

Page({
	/*
	* 页面的初始数据
	*/
	data: {
		numberI,
		codeNum: 3,
		code: '',
		codeType,
		codeT: 1,
		inputVal: ''
	},

	// 校验验证码
	checkCode() {
		this.data.inputVal == this.data.code ? showToast('验证码匹配正确') : showToast('验证码匹配错误');
	},

	// 获取输入框的值
	inputF({ detail: { value } }) {
		this.setData({
			inputVal: value
		})
	},

	// 单选
	radioChange({ detail: { value } }) {
		this.setData({
			codeT: value
		});
	},

	// 选择验证码位数
	tabBar({ currentTarget: { dataset: { item } } }) {
		this.setData({
			codeNum: item,
			code: ''
		});
	},

	// 生成验证码
	verificationCode() {
		let code = '';
		let random = [];
		let num = 10;

		switch (Number(this.data.codeT)) {
			case 1:
				random = numbers;
				num = 10;
				break;
			case 2:
				random = lowercaseLetters;
				num = 26;
				break;
			case 3:
				random = capital;
				num = 26;
				break;
			case 4:
				random = [...lowercaseLetters, ...capital];
				num = 52;
				break;
			case 5:
				random = [...numbers, ...capital];
				num = 36;
				break;
			case 6:
				random = [...numbers, ...lowercaseLetters];
				num = 36;
				break;
			default:
				random = [...numbers, ...capital, ...lowercaseLetters];
				num = 62;
				break;
		}

		for (let i = 0; i < this.data.codeNum; i++) {
			let index = Math.floor(Math.random() * num);
			code += random[index];
		}

		this.setData({
			code,
			inputVal: ''
		});
	}
})

声明

3.1、数据从 .js 文件中导入,不展示。
3.2、公共样式不展示。
3.3、联系:MJ2506562048


web前端之微信小程序实现随机数功能、获取幸运数、Number、parseInt、random

html部分

<view>
	<!-- 幸运数字范围 -->
	<view class="text_align_center padding_tb_26 font_size_32">
		<text class="color_blue font_weight_700"> {{minVal}}</text><text class="color_blue font_weight_700"> {{maxVal}}</text>
	</view>

	<!-- 输入框 -->
	<view>
		<inputBox parameters="{{sValueTransfer}}" bind:myEvent="startOnMyEvent"></inputBox>
		<inputBox parameters="{{eValueTransfer}}" bind:myEvent="endOnMyEvent"></inputBox>
	</view>

	<!-- 幸运转盘 -->
	<view class="border_width margin_lr_auto width_200 height_200 line_height_200 text_align_center radius_100_ font_weight_600 {{luckNumbers!='倒计时'?'font_size_40':''}} {{textColor}}" style="border-color:{{colors}}" catchtap="startNumber">{{luckNumbers}}</view>
</view>

JavaScript部分

const {
  globalData: {
    showToast
  }
} = getApp();

Page({

  /**
   * 页面的初始数据
   */
  data: {
    // 传给组件的参数
    sValueTransfer: {
      // 输入框提示
      pla: "请输入开始值",
      // 校验规则
      // 1 纯数字,且第一位不能为 0
      verificationRules: 1,
      // 输入的最大长度
      maxL: 3
    },
    // 传给组件的参数
    eValueTransfer: {
      // 输入框提示
      pla: "请输入结束值",
      // 校验规则
      // 1 纯数字,且第一位不能为 0
      verificationRules: 1,
      // 输入的最大长度
      maxL: 3
    },
    minVal: 1,
    maxVal: 10,
    luckNumbers: '倒计时',
    colors: 'red forestgreen blue cyan',
    textColor: '',
    isSetTime: true
  },

  // 开始倒计时
  startNumber() {
    let minVal = Number(this.data.minVal),
      maxVal = Number(this.data.maxVal),
      arrColor = ['red forestgreen blue cyan', 'cyan red forestgreen blue', 'blue cyan red forestgreen', 'forestgreen blue cyan red'],
      count = 2,
      sumCount = 2;
    if (!this.data.isSetTime) return showToast('上次倒计时未结束');
    if (minVal >= maxVal) return showToast('开始值要小于结束值');
    this.setData({
      textColor: '',
      isSetTime: false
    });
    this.setData({
      colors: arrColor[1],
      luckNumbers: sumCount
    });
    let clearTime = setInterval(() => {
      sumCount -= 1;
      this.setData({
        colors: arrColor[count],
        luckNumbers: sumCount
      });
      if (sumCount == -1) {
        this.setData({
          luckNumbers: parseInt(Math.random() * (maxVal - minVal + 1) + minVal, 10),
          textColor: 'color_theme',
          isSetTime: true
        });
        clearInterval(clearTime);
      }
      if (count == 3) {
        count = -1
      };
      count += 1;
    }, 600);
  },

  // 收集开始值
  startOnMyEvent({
    detail: {
      value
    }
  }) {
    this.setData({
      minVal: value || 1
    });
  },

  // 收集结束值
  endOnMyEvent({
    detail: {
      value
    }
  }) {
    this.setData({
      maxVal: value || 10
    });
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

css部分

.border_width {
  border-style: solid;
  border-width: 100rpx 100rpx 100rpx 100rpx;
}

JavaScrip之寻找min到max中有多少个tag匹配目标

function seek(min = 0, max = 100, tag = '9') {
    tag = String(tag);

    let num = 0;

    for (let i = min; i <= max; i++) {
        let str = String(i);

        for (let j = 0; j < str.length; j++) if (str[j] === tag) num++;
    }

    return num;
}

console.log(seek(100, 399, 0));

JavaScrip之任务队列和事件循环

1、任务(事件)队列

JavaScript中有两类任务(事件)队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。


1.1、宏仁务(macro tasks)

1. setTimeout
2. setInterval
3. script
4. I/O
5. UI 交互事件
6. postMessage
7. MessageChannel
8. setImmediate(Nodejs环境)


1.2、微任务(micro tasks)

1. Promise.then
2. Object.observe
3. MutaionObserve
4. process.nextTick(Node.js环境)


2、事件循环

事件循环(Event Loop)遵循的是HTML5的标准。当执行栈(stack)为空的时候,就会从任务队列中,取任务来执行。共3步:
1、取一个宏任务来执行。执行完毕后,下一步。
2、取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
3、更新UI渲染。
事件循环(Event Loop)会无限循环执行上面的3步,这就是事件循环(Event Loop)的主要控制逻辑。其中,第3步(更新UI渲染)会根据浏览器的逻辑,决定要不要马上执行更新。毕竟更新UI成本大,所以,一般都会比较长的时间间隔,执行一次更新。


JavaScript中值在各种场景的转换规则

字符串操作环境数字运算环境逻辑运算环境对象操作环境
undefined“undefined”NaNfalseError
null“null”0falseError
非空字符串不转换字符串对应的数字值true
空字符串不转换0falseString
0“0”不转换falseNumber
NaN“NaN”不转换falseNumber
Infinity“Infinity”不转换trueNumber
Number.POSITIVE_INFINITY“Infinity”不转换trueNumber
Number.NEGATIVE_INFINITY“-Infinity”不转换trueNumber
Number.MAX_VALUE“1.7976931348623157e+308”不转换trueNumber
Number.MIN_VALUE“5e-324”不转换trueNumber
其他所有数字“数字的字符串值”不转换trueNumber
true“true”1不转换Boolean
false“false”0不转换Boolean
对象toString()value()或toString()或NaNtrue不转换

双for循环

1、在原来数组上加字段

此字段有两种状态。不改变原来数组。

let originData = [
	{
		"id": 1,
		"value": "近水楼台先得月,向阳花木易为春。"
	},
	{
		"id": 2,
		"value": "路漫漫其修远兮,吾将上下而求索。"
	},
	{
		"id": 3,
		"value": "落霞与孤鹜齐飞,秋水共长天一色。"
	},
	{
		"id": 4,
		"value": "穷则独善其身,达则兼济天下。"
	},
	{
		"id": 5,
		"value": "沧海月明珠有泪,蓝田日暖玉生烟。"
	}
];
let mateData = [2, 3, 5];

let mapFor = function(originData, mateData) {
	let result = originData.map(item => {
		let items = { ...item, title: 0 };
		for (let i = 0; i < mateData.length; i++) {
			const element = mateData[i];
			if (item.id == element) {
				items.title = 1;
				continue;
			}
		}
		return items;
	});
	console.log('mapFor:', result);
};

let mapSome = function(originData, mateData) {
	return originData.map(item => {
		let items = { ...item, title: 0 };
		mateData.some(_ => _ === item.id) && (items.title = 1);
		return items;
	});
};

// 方案一
mapFor(originData, mateData);

// 方案二
console.log(mapSome(originData, mateData));

2、在原来数组上分出两个数组

不改变原来数组。

let originData = [
	"近水楼台先得月,向阳花木易为春。",
	"路漫漫其修远兮,吾将上下而求索。",
	"落霞与孤鹜齐飞,秋水共长天一色。",
	"穷则独善其身,达则兼济天下。",
	"沧海月明珠有泪,蓝田日暖玉生烟。"
];
let mateData = ["路漫漫其修远兮,吾将上下而求索。", "沧海月明珠有泪,蓝田日暖玉生烟。"],
	arr1 = [],
	arr2 = [];

originData.forEach((item) => {
	for (let i = 0; i < mateData.length; i++) {
		if (item == mateData[i]) {
			arr1.push(item);
			return false;
		}
	}
	arr2.push(item);
});
console.log('arr1', arr1); 
// ["路漫漫其修远兮,吾将上下而求索。", "沧海月明珠有泪,蓝田日暖玉生烟。"]
console.log('arr2', arr2); 
// ["近水楼台先得月,向阳花木易为春。", "落霞与孤鹜齐飞,秋水共长天一色。", "穷则独善其身,达则兼济天下。"]

通过promise获取数据后的解析

1、错误写法

data.forEach((item) => {
	this.form.options.push({
		id: item.id,
		names: item.names,
		children: getPromise(item.id),
	});
});

2、正确写法

data.forEach(async (item) => {
	const { result } = await getPromise(item.id);
	
	this.form.options.push({
		id: item.id,
		names: item.names,
		children: result ? result : [],
	});
});

对象的构造函数和类

创建对象

// 使用字面量创建对象
let starL = {
    Uname: '刘邦',
    Uage: 30
};
console.log(starL);

// 使用构造函数创建对象
function Star(Uname, Uage) {
	this.Uname = Uname;
	this.Uage = Uage;
};
let starW = new Star('王之涣', 23); // 实例化对象
console.log(starW);

创建类

class Poet {
	// 类的共有属性放到 constructor 里面,
	// constructor 是构造器或者构造函数
	constructor(Uname, age) {
		this.Uname = Uname;
		this.age = age;
    };
    // 注意: 方法与方法之间不需要添加逗号
    humVerse(song) {
		console.log(this.Uname + '吟' + song);
    };
};
var PoetW = new Poet('王安石', 26);
console.log(PoetW);
PoetW.humVerse('元日');

向Number内置类的原型上扩展方法

编写plus和minus实现如下需求

// anonymous: 匿名函数的名称
// 闭包的作用是防止函数跟全局函数有冲突
// 函数自调方式
// ~ function() {}();
// (function() {})()
~ function anonymous(proto) {
	// 封装 plus 函数 和 minus 函数 共同部分
    // 使用函数表达式方式创建函数是为了保证函数提升和
    // 防止在创建函数前调用函数
	const checkNum = function checkNum(num) {
		// 强制转换为 Number 类型
		num = Number(num);
		// 如果 num 不是有效数字
		if (isNaN(num)) {
			// 给 num 赋值为 0
			num = 0;
		}
		// 返回 num
		return num;
	};
	
	// 函数表达式
    // 把匿名函数改为具名函数,在全局通过函数名无法访问此函数
	proto.plus = function plus(num) {
		// 在函数内部可以调用此函数
        // 调用方式 Number.prototype.plus()
        // 既不跟外部函数名字冲突
        // 又可以通过特殊方式调用
        // => this: 要操作的那个数字实例(对象) 
        // => 返回 Number 类的实例,实现链式写法
		return this + checkNum(num);
	};
	
	proto.minus = function minus(num) {
		return this - checkNum(num);
	};
}(Number.prototype);

let n = 10;
let m = n.plus(10).minus(5);
console.log(m); // => 15 (10+10-5)
// 向 Number 内置类(原型)上扩展方法
// 创建一个数据类型值:
// 1.字面量方式
// 2.构造函数方式
// 不论哪一种方式,创建出来的结果都是所属类的实例
// => 基本数据类型两种创建方式是不一样的: 字面量创建的是基本类型值,
// 构造函数方式创建的是引用类型值
// let x = 10; // 字面量方式
// let y = new Number(10); // 构造函数方式
// console.log(y.valueof() === x);
// => 对象结果的原始值是基本类型数字 10

判断是否给实例设置属性name

function C1(name) {
	// => name: undefined 没有给实例设置私有的属性name
	if (name) this.name = name;
}

function C2(name) {
	// => 给实例设置私有属性 name this.name = undefind
	this.name = name;
}

function C3(name) {
	// => 给实例设置私有属性 name this.name = undefind
	// 因为 name 的值为 undefined
	// 所以走 || 后面的代码
	this.name = name || 'join';
}

C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';

console.log((new C1().name) + (new C2().name) + (new C3().name));

// new C1().name
// 没有传参 函数里面的 name 为 undefined
// 实例里面没有 name 找原型上的 'Tom'

// new C2().name 
// 没有传参 函数里面的的 name 为 undefined
// 但是函数确实添加了 name 属性 只是值为 undefined 而已
// 所以找私有属性 name 的值为 undefined

// new C3().name
// 没有传参 函数里面的 name 为 undefined
// 但是函数确实添加了 name 属性 只是值为 'join'
// 所以私有属性 name 的值为 'join'

// 最终结果为 'Tomundefinedjoin'

浅浅克隆和浅克隆(clone)–01

let oldObject = {
	name: '西施',
	age: 26,
	height: '163.30',
	obj: {
		city: '成都',
		street: '玉林路'
 	},
	array: ['赵雷', '嬴政', '天明']
};    

// 浅克隆 -- 只能处理一层数据结构的克隆
// 深克隆 -- 可处理超过一层或多层数据结构的克隆
function clone(oldObject) {
	let newObject = {};
	for (let key in oldObject) {
		newObject[key] = oldObject[key];
	};
	return newObject;
};

// 浅浅克隆
// 引用同一个地址
let newData = oldObject;
console.log(oldObject === newData); // true

// 浅克隆成功
console.log(oldObject == clone(oldObject)); // false

let cloneData = clone(oldObject);

// 第一层可以克隆成功
cloneData.age = 30;
console.log('cloneData:', cloneData.age); // 30
console.log('oldObject:', oldObject.age); // 26

// 第二层 对象
cloneData.obj.city = '杭州';
console.log('cloneData:', cloneData.obj.city); // 杭州
console.log('oldObject:', oldObject.obj.city); // 杭州

// 第二层 数组
cloneData.array[1] = '荆轲';
console.log('cloneData:', cloneData.array[1]); // 荆轲
console.log('oldObject:', oldObject.array[1]); // 荆轲

深克隆(clone)–01

let oldObject = {
	name: '西施',
	age: 26,
	height: '163.30',
	score: null,
	friends: ['李白', '杜牧', '李商隐'],
	oldObj: {
		city: '广州',
		area: '越秀区',
		street: '明信路'
	}
};

function clone(oldObject) {
	// 第一步:创建一个空对象
	let newObject = {};
	// 第二步:判断值是否为空
	// 如果给的值里面有 null 就直接返回 null
	// 如果不进行处理会输出 {} 空对象
	if (oldObject === null) {
		return null;
	}
	// 第三步:判断是否是数组
	// 如果是数组需要处理,
	// 如果不处理数组会转为类数组对象
	// { 0: "李白", 1: "杜牧", 2: "李商隐" }
	if ({}.toString.call(oldObject) === '[object Array]') {
		var newArray = [];
		// 数组克隆
		// 使用 splice() 方法实现
		// splice() 里面什么也没写,
		// 表示从数组第一项截取到末尾
		newArray = oldObject.slice();
		return newArray;
	}
	// 第四步:使用 for in 循环实现克隆
	for (let key in oldObject) {
		// 如果原对象属性值是原始类型,
		// 可以直接赋值
		// 原始类型值是直接复制副本
		if (typeof oldObject[key] !== 'object') {
			newObject[key] = oldObject[key];
		} else {
			// 当前属性不是原始类型值,
			// 再次调用(类似与递归) clone 函数,
			// 继续复制当前属性的值
			newObject[key] = clone(oldObject[key]);
		}
	}
	return newObject;
};

// 如果是 false 说明克隆成功
console.log(oldObject === clone(oldObject)); // false

let cloneData = clone(oldObject);

// 修改克隆后的第一层 age 的值
cloneData.age = 30;

// 修改克隆后第二层 数组的值
cloneData.friends[2] = '杜甫';

// 修改克隆后的二层 city 的值
cloneData.oldObj.city = '武汉';

// 深克隆,第一层两个值的地址已经完全不一样了
console.log('oldObject:', oldObject.age); // 26
console.log('newObject:', cloneData.age); // 30

// 深克隆,第二层 数组 两个值的地址已经完全不一样了
console.log('oldObject:', oldObject.friends[2]); // 李商隐
console.log('newObject:', cloneData.friends[2]); // 杜甫

// 深克隆,第二层 对象 两个值的地址已经完全不一样了
console.log('oldObject:', oldObject.oldObj.city); // 广州
console.log('newObject:', cloneData.oldObj.city); // 武汉

数据类型转换

在JavaScript中类型转换只有三种情况。
1、转换为布尔值,调用Boolean()方法。
2、转换为数字,调用Number()、parseInt()和parseFloat()方法。
3、转换为字符串,调用.toString()或者String()方法。


==(两等) 和 !(非) 的优先级比较

console.log([] == ![]); // true

1、!(非) 的优先级高于==(两等) ,右边[]是true,取返等于false。
2、一个引用类型和一个值去比较,会把引用类型转化成值类型。所以,[] => 0 。
3、最后0 == false的结果是true 。


访问器属性-13

let attribute = {
	id: 11,
	sname: '名字'
};

// 给 attribute 对象添加两个属性:_age 和 age
// age 作为保镖保护 _age
// 第一步
Object.defineProperties(attribute, {
	// 先添加一个半隐藏的 _age
	_age: {
		// 值
		value: 26,
		// 可以改
		writable: true,
		// 半隐藏
		enumerable: false,
		// 双保险
		configurable: false,
	},

	// 再为 _age 添加一个保镖 -- 访问器属性
	age: {
		get: function() {
			return this._age;
		},
		set: function(value) {
			if (value >= 18 && value <= 65) {
				this._age = value;
			} else {
				throw Error('年龄必须介于18~65之间。');
			}
		},
		enumerable: true,
		configurable: false
	}
});

console.log(attribute);
// {id: 11, sname: "名字", _age: 26}

console.log(attribute.age);
// 26

console.log(attribute._age);
// 26

attribute.age = 27;
console.log(attribute.age);
// 27

attribute.age = 16;
console.log(attribute.age);
// Uncaught Error: 年龄必须介于18~65之间。

变量提升案例

function funHoisting() {
	console.log(1);
};

funHoisting(); // 2

function funHoisting() {
	console.log(2);
};

funHoisting(); // 2

var funHoisting = 100;
// 在函数提升的时候 var fun 对程序没有任何影响,
// 等于没写
funHoisting();
// Uncaught TypeError: funHoisting is not a function

var、let和const的区别

1、var声明的变量会挂载在window上,而let和const声明的变量不会。
2、var声明变量存在变量提升,let和const不存在变量提升。
3、let和const声明形成块作用域。
4、同一作用域下var可以声明同名变量,而let和const不可以。


一维数组升为二维数组

function arrTrans(num, arr) {
	// 参数 num : 决定二维数组的个数
	// 参数 arr : 源数组
	const iconsArr = [];
	
	arr.forEach((item, index) => {
		// floor() 向下取整
		const page = Math.floor(index / num);
		// 向数组 iconsArr 添加数组
		// 数组下标就是 page
		if (!iconsArr[page]) iconsArr[page] = [];
		
		iconsArr[page].push(item);
	});
	
	return iconsArr;
}

console.log(arrTrans(5, [1, 1, 2, 3, 4, 5, 6, 7, 8]));
// [[1, 1, 2, 3, 4], [5, 6, 7, 8]]

promise的使用

function func1() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("func1 1000");
    }, 1000);
  });
};

function func2() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("func1 2000");
    }, 2000);
  });
};

function func3() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("func1 3000");
    }, 3000);
  });
};

func1()
  .then((result) => {
    console.log(result);
    return func3();
  })
  .then((result) => {
    console.log(result);
    return func2();
  })
  .then((result) => {
    console.log(result);
  });

字符串去重(以逗号隔开的字符串)

function stringDuplicateRemoval(str) {
	let obj = {},
		arr = str.split(","),
		result = [],
		i = 0;
		
	for (let i = 0; i < arr.length; i++) obj[arr[i]] = 1;

	for (result[i++] in obj);
	
	return result;
}

console.log(stringDuplicateRemoval('1,2,3,3'));
// [1,2,3]

console.log(stringDuplicateRemoval('1,2,3,3').toString());
// '1,2,3'

字符串去重

function stringDuplicateRemoval(str) {
	let newStr = "";
	
	for (let i = 0; i < str.length; i++) {
		if (newStr.indexOf(str[i]) == -1) newStr += str[i];
	}
	
	return newStr;
}

console.log(this.stringDuplicateRemoval("110120140"));
// 1024

数组对象排序

let arr = [
	{
		id: 1,
		weather:'晴'
	}, 
	{
		id: 2,
		weather:'小雨'
	}, 
	{
		id: 3,
		weather:'大雨'
	}, 
	{
		id: 4,
		weather:'小雨'
	}, 
	{
		id: 5,
		weather:'阴'
	}, 
	{
		id: 6,
		weather:'雪'
	}
];

function sortData(a, b) {
	return b.id - a.id;
}

arr.sort(sortData);

console.log(arr);

正则表达式实现简单模板数据填充

let templateStr = '<h1>我买了一棵{{thing}},花了{{money}}元,好{{mood}}</h1>';
let data = {
	thing: '白菜',
	money: 5,
	mood: '激动'
};

// 最简单的模板引擎的实现机理,
// 利用的是正则表达式中的 replace() 方法。
// replace() 的第二个参数可以是一个函数,
// 这个函数提供捕获的东西的参数,就是$1
// 结合 data 对象,即可进行智能的替换
function render(templateStr, data) {
	return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
		return data[$1];
	});
}

console.log(render(templateStr, data));
// <h1>我买了一棵白菜,花了5元,好激动</h1>

JavaScript获取字符串(string)第一个字符

let stringVal = "web半晨";
// 方式一
console.log(stringVal.charAt(0));
// 方式二
console.log(stringVal.substring(0, 1));
// 方式三
console.log(stringVal.substr(0, 1));

JavaScript递归实现数组扁平化(数组降维)

let recursionData = [
	{
		id: 1,
		name: "一级",
		children: [
			{
				id: 2,
				name: "二级-1",
				children: [
					{
						id: 7,
						name: "三级-1",
						children: [
							{
								id: 10,
								name: "四级-1",
							},
						],
					},
					{
						id: 8,
						name: "三级-2",
					},
				],
			},
			{
				id: 3,
				name: "二级-2",
				children: [
					{
						id: 5,
						name: "三级-3",
					},
					{
						id: 6,
						name: "三级-4",
					},
				],
			},
			{
				id: 4,
				name: "二级-3",
				children: [
					{
						id: 9,
						name: "三级-5",
						children: [
							{
								id: 11,
								name: "四级-2",
							},
						],
					},
				],
			},
		],
	},
],
	arr = [];

function funRecursion(list) {
	for (let i in list) {
		arr.push({
			id: list[i].id,
			name: list[i].name,
		});

		if (list[i].children) funRecursion(list[i].children);
	}
	
	return arr;
}

console.log(funRecursion(recursionData));
// [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]

console.log(arr.map((item) => item.id).sort((a, b) => a - b));
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

JavaScript中number和~~比较、API

function replaceNumber() {
  console.log(Number(null)); // 0
  console.log(~~null); // 0
  console.log("--------------------------------------------------------------");

  console.log(Number(undefined)); // NaN
  console.log(~~undefined); // 0
  console.log("--------------------------------------------------------------");

  console.log(Number(123)); // 123
  console.log(~~123); // 123
  console.log("--------------------------------------------------------------");

  console.log(Number("123")); // 123
  console.log(~~"123"); // 123
  console.log("--------------------------------------------------------------");

  console.log(Number("abc")); // NaN
  console.log(~~"abc"); // 0
  console.log("--------------------------------------------------------------");

  console.log(Number(NaN)); // NaN
  console.log(~~NaN); // 0
}

replaceNumber();

JavaScript实现单词、字母排序

function alphabeticSorting(params) {
	return params.sort((a, b) => {
		let x = a.word,
			y = b.word;
			
		return x > y ? 1 : x < y ? -1 : 0;
	});
}

let words = [
	{
		id: 1,
		word: "absolute",
	},
	{
		id: 2,
		word: "every",
	},
	{
		id: 3,
		word: "display",
	},
	{
		id: 4,
		word: "border",
	},
	{
		id: 5,
		word: "class",
	},
	{
		id: 6,
		word: "background",
	},
	{
		id: 7,
		word: "delete",
	},
];

console.log(alphabeticSorting(words));

JavaScript实现分组排序单词、单词分组排序、不能实现中文首拼音排序

function groupSortWords(params) {
    let grouping = {},
        afterGrouping = [];

    params.forEach(item => {
    	// 单词首字母转大写
        let capital = item.word.replace(/\b[a-zA-Z]+\b/g, items => {
            return items.charAt(0).toUpperCase();
        });

        // grouping[capital] 使用了对象不能有同一属性的原理
        // 相当于去重
        if (grouping[capital]) {
            grouping[capital].arr.push(item);
        } else {
            grouping[capital] = {
                groupingName: `${capital}`,
                arr: [item]
            }
        }
    });

    // 把对象变为数组
    for (const key in grouping) {
        if (Object.hasOwnProperty.call(grouping, key)) {
            const itemKye = grouping[key];
            afterGrouping.push(itemKye);
        }
    }

    // 外层数组排序
    afterGrouping = afterGrouping.sort((a, b) => {
        let x = a.groupingName,
            y = b.groupingName;
        return x > y ? 1 : x < y ? -1 : 0;
    });

    // 内层数组排序
    afterGrouping = afterGrouping.map(item => {
        item.arr = item.arr.sort((a, b) => {
            let x = a.word,
                y = b.word;
            return x > y ? 1 : x < y ? -1 : 0;
        });
        return item;
    })

    // 最终返回
    return afterGrouping;
}

// 数据源
let words = [
    {
        id: 1652156,
        word: "absolute",
    },
    {
        id: 2365256,
        word: "every",
    },
    {
        id: 3156258,
        word: "display",
    },
    {
        id: 4695845,
        word: "border",
    },
    {
        id: 5125369,
        word: "class",
    },
    {
        id: 6985485,
        word: "background",
    },
    {
        id: 7156895,
        word: "delete",
    },
    {
        id: 8789651,
        word: "color",
    },
    {
        id: 9369529,
        word: "application",
    },
    {
        id: 1031562,
        word: "length",
    },
];

// 调用函数
console.log(groupSortWords(words));

JavaScript之逻辑运算、||、&&

// 1、JavaScript中返回false的值
console.log(7 == undefined); // false
console.log(7 == null); // false
console.log(7 == NaN); // false
console.log(7 == ''); // false
console.log(7 == 0); // false
console.log(7 == false); // false
console.log(7 == []); // false
console.log(7 == {}); // false

// 2、逻辑运算
// 2.1、&&(且)
console.log(1 && 2); // 2 如果都是真,返回最后的值
console.log(1 && 2 && undefined && 10); // undefined 如果遇到假,直接返回值

// 2.2、||(或)
console.log(1 || 2); // 1 如果遇到真,直接返回值
console.log(undefined || NaN || null); // null 如果遇到假,一直判断到最后,最终返回最后一个

// 2.3、!(非)
console.log(!1); // false
console.log(!!1); // true
console.log(7 != 7); // false
console.log(7 != !7); // true

JavaScript之函数的实际参数(实参)和形式参数(形参)、arguments(实参)、(a, b, c, d实参)、字面量定义具名函数

let test = function testName(a, b, c) {
	console.log('实参个数:', arguments.length);
	// 实参个数: 2
	console.log('函数形参个数:', testName.length);
	// 函数形参个数: 3

	// 基本类型数据-----------------------------------------
	// 通过形参方式修改
	a = 2;
	console.log('实参 a:', arguments[0]);
	// 实参 a: 1
	console.log('形参 a:', a);
	// 形参 a: 2

	// 通过实参方式修改
	arguments[0] = 3;
	console.log('实参 a:', arguments[0]);
	// 实参 a: 3
	console.log('形参 a:', a);
	// 形参 a: 2

	// 基本类型数据的实参与形参变化互不影响

	// 引用类型数据-----------------------------------------
	// 通过形参方式修改
	b.a = 6;
	console.log('实参 b:', arguments[1].a);
	// 实参 b: 6
	console.log('形参 b:', b.a);
	// 形参 b: 6

	// 通过实参方式修改
	arguments[1].a = 7;
	console.log('实参 b:', arguments[1].a);
	// 实参 b: 7
	console.log('形参 b:', b.a);
	// 形参 b: 7

	// 引用类型数据的实参与形参变化相互影响,
	// 因为使用的是引用地址关系

	// 实参与形参的映射-------------------------------------
	c = 9;
	console.log('实参 c:', arguments[2]);
	// 实参 c: undefined
	console.log('形参 c:', c);
	// 形参 c: 9

	arguments[2] = 5;
	console.log('实参 c:', arguments[2]);
	// 实参 c: 5
	console.log('形参 c:', c);
	// 形参 c: 9

	// 实参个数少于形参个数时
	// 多出来的形参个数与实参个数不能形成映射
	// 所以实参个数为undefined

	return { a, b, c };
}

console.log(test(1, { a: 1 }));
// {a: 2, b: {…}, c: 9}

JavaScript之使用字面量定义具名函数,且实现递归

// 定义退出递归的条件,
// 否则进入无限循环
let a = 0;

let test = function testName() {
	a += 1;
	if (a == 3) return;
	testName();
}

console.log(test(0));
// undefined
testName();
// ReferenceError: testName is not defined

// 使用字面量定义具名函数,
// 在函数外部无法通过名字调用函数,
// 只能通过变量调用函数

JavaScript之形参默认值、实参、undefined占位

function test(a = 1, b = 2) {
	// ES5不支持形参默认值的解决方法
	// 曲线救国
	a = arguments[0] || 1;
	b = arguments[1] || 2;
	console.log(a);
	console.log(b);
}

test(undefined, 3);
// 此处是因为只需要传第二参数
// 因为形参与实参是一一对应,
// 所以需要使用undefined来占位

JavaScript校验特殊字符、indexOf、substr

function specialCharacters(str) {
	if (!str) return true;

	const specialKey = "[`~!#$^&*()=|{}': ; '\\[\\].<>/?~!#¥……&*()——|{}【】‘;:”“'。,、?‘']";
	for (var i = 0; i < str.length; i++) if (specialKey.indexOf(str.substr(i, 1)) != -1) return false;

	return true;
}

specialCharacters('哈.哈') ? console.log('校验通过') : console.error('存在特殊字符!');

JavaScript之合并两个对象、Object、assign

function mergeTwoObjects(params1, params2) {
    if (!params1 || !params2) return '参数不能为空!';
    return Object.assign(params1, params2);
}

console.log(mergeTwoObjects({a: 1, b: 3}, {c: 6, b: 5}));
// {a: 1, b: 5, c: 6}
console.log(mergeTwoObjects({a: 1, b: 3}, [6, 5]));
// {0: 6, 1: 5, a: 1, b: 3}
console.log(mergeTwoObjects([1, 3], {a: 6,b: 5}));
// [1, 3, a: 6, b: 5]
console.log(mergeTwoObjects([1, 3], [6, 5]));
// [6, 5]

JavaScript之中划线、下划线隔开的变量转为小驼峰、横杠、replace、substr、toUpperCase、正则、RegExp

function camelCase(params) {
	// 正则匹配到两个字符
	// 使用substr获取最后一个字符
	// 再使用toUpperCase转为大写
    return params.replace(/[_-][a-zA-z]/g, str => str.substr(-1).toUpperCase());
}

console.log(camelCase('user_name'));
// userName
console.log(camelCase('user-number'));
// userNumber
console.log(camelCase('user_Password'));
// userPassword
console.log(camelCase('user-Age'));
// userAge

深克隆(clone)–02

1、数据

let oldData = {
  name: '西施',
  age: 26,
  height: '163.30',
  score: null,
  friends: ['李白', '杜牧', '李商隐'],
  oldObj: {
    city: '广州',
    area: '越秀区',
    street: '明信路',
  },
};

2、克隆函数

function deepClone(data) {
  	// 第一步:判断传进来的数据是否为空 或者 是基本类型的值
  	if (data === null || typeof data !== 'object') return data;
  	// 第二步:判断数据是否是数组
  	// 如果是数组创建一个空数组
	// 否则创建一个空对象
  	let cloneData = Array.isArray(data) ? [] : {};
	// 第三步:使用 for in 循环遍历数据
  	for (let key in data) {
  		// 判断结束递归
    	if (data.hasOwnProperty(key)) {
      		// Object.prototype.hasOwnProperty() 方法会返回一个布尔值,
      		// 指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键) 
      		// deepClone(data[key]); 使用了函数递归
      		cloneData[key] = deepClone(data[key]);
    	}
	}
  	return cloneData;
};

3、验证

// 克隆后的数据
let cloneData = deepClone(oldData);

// 如果是 false 说明克隆成功
console.log(oldData === cloneData); // false

// 修改克隆后的第一层 age 的值
cloneData.age = 32;

// 修改克隆后第二层 数组的值
cloneData.friends[2] = '杜甫';

// 修改克隆后的二层 city 的值
cloneData.oldObj.city = '武汉';

// 深克隆,第一层两个值的地址已经完全不一样了
console.log('oldObject:', oldData.age); // 26
console.log('newObject:', cloneData.age); // 32

// 深克隆,第二层 数组 两个值的地址已经完全不一样了
console.log('oldObject:', oldData.friends[2]); // 李商隐
console.log('newObject:', cloneData.friends[2]); // 杜甫

// 深克隆,第二层 对象 两个值的地址已经完全不一样了
console.log('oldObject:', oldData.oldObj.city); // 广州
console.log('newObject:', cloneData.oldObj.city); // 武汉

函数柯理化-01

1、简单示例 add 函数

// Currying 前
function add(x, y) {
    return x + y;
};

// Currying 后
function curryingAdd(x) {
    return function (y) {
        return x + y;
    };
};

add(1, 2); // 3
curryingAdd(1)(2); // 3

实际上就是把 add 函数的 x, y 两个参数变成了先用一个函数接收 x 然后返回一个函数去处理 y 参数。就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。


2、参数复用

// Currying 前
function check(reg, txt) {
    return reg.test(txt);
};

check(/\d+/g, 'test'); // false
check(/[a-z]+/g, 'test'); // true

// Currying 后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt);
    };
};

let hasNumber = curryingCheck(/\d+/g);
let hasLetter = curryingCheck(/[a-z]+/g);

hasNumber('test1'); // true
hasNumber('testtest'); // false
hasLetter('21212'); // false

示例是一个正则的校验,正常来说直接调用 check 函数就可以,但是如果有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber,hasLetter 等函数,让参数能够复用,调用起来也更方便。


3、提前确认
3.1、示例壹

let on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        };
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        };
    };
};

3.2、示例贰

let on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            };
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            };
        };
    };
})();
// ()(); => ~function() {.. ..}();

3.3、示例叁

// 上面就是把 isSupport 这个参数给先确定下来了
let on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    };
};

在做项目的过程中,封装一些 dom 操作可以说再常见不过,上面第一种写法也是比较常见,但是看看第二种写法,它相对于第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。


4、延迟运行

Function.prototype.bind = function (context) {
    let that = this;
    let args = Array.prototype.slice.call(arguments, 1);
 
    return function() {
        return that.apply(context, args);
    };
};

js 中经常使用的 bind,实现的机制就是 Currying。


5、通用的封装方法
5.1、初步封装

let currying = function(fn) {
    // args 获取第一个方法内的全部参数
    let args = Array.prototype.slice.call(arguments, 1);
    return function() {
        // 将后面方法里的全部参数和 args 进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments));
        // 把合并后的参数通过 apply 作为 fn 的参数并执行
        return fn.apply(this, newArgs);
    };
};

通过闭包把初步参数给保存下来,然后通过获取剩下的 arguments 进行拼接,最后执行需要 currying 的函数。但是,有缺陷,这样返回的只能多扩展一个参数,currying(a)(b)© 这样的话,就不支持多参数调用了。


5.2、递归封装

function progressCurrying(fn, args) {
    let that = this;
    let len = fn.length;
    let args = args || [];
    
    return function() {
        let _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);
        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(that, fn, _args);
        }
        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    };
};

其实是在初步的基础上,加上了递归的调用,只要参数个数小于最初的fn.length,就会继续执行递归。


6、经典面试题

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    let _args = Array.prototype.slice.call(arguments);
    
    // 在内部声明一个函数,
    // 利用闭包的特性保存_args并收集所有的参数值
    let _adder = function() {
        _args.push(...arguments);
        return _adder;
    };
    
    // 利用toString隐式转换的特性,
    // 当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    };
    return _adder;
};

add(1)(2)(3);               // 6
add(1, 2, 3)(4);            // 10
add(1)(2)(3)(4)(5);         // 15
add(2, 6)(1);               // 9

柯理化函数之参数复用

1、正常函数封装

function check(reg, txt) {
    return reg.test(txt);
}

check(/\d+/g, 'test');       // false
check(/[a-z]+/g, 'test');    // true

缺点:每次调用都要多传一个参数


2、Currying后

function curryingCheck(reg) {
	// 使用闭包机制保存 reg 值
	// 形成多个执行上下文
	// 通过外部调用的引用,使其作用域无法释放
    return (txt) => reg.test(txt);
}

// 使用变量接收外部函数返回的内部函数
// 在调用外部函数的时候传递参数,并且参数会保存在闭包中
// 当调用内部函数时会通过作用域链机制找到外层函数保存的变量
let hasNumber = curryingCheck(/\d+/g);
let hasLetter = curryingCheck(/[a-z]+/g);

// 此时调用的是返回的内部函数
hasNumber('test1');      // true
hasNumber('testtest');   // false
hasLetter('21212');      // false

示例是一个正则的校验,正常来说直接调用 check 函数就可以,但是如果有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber,hasLetter 等函数,让参数能够复用,调用起来也更方便。


面向对象的三大特性

1、封装
1.1、如果一个对象不需要反复创建

var object = {
	Uname: '张三',
	Uage: 56
};

1.2、如果一个对象需要反复创建,使用构造函数即可

// 第一步:定义构造函数
function Student(name, age) { 
	this.name = name; 
	this.age = age; 
	// 不能将方法定义在构造函数中,
	// 因为会被多次创建,每创建一个实例就会创建一次方法。
};

// 第二步:用 new 调用构造函数
var newFun = new Student();

2、继承
所有子对象共有的方法,应该添加到构造函数的原型对象中,子对象调用方法时,先在子对象本地查找。如果本地对象没有找到,才延原型链向父级对象查找,直到找到为止。


3、多态
如果从父对象继承来的方法不好用,可在对象本地定义同名方法,覆盖父对象中的方法(重写)。

强调:原型对象中,方法 (函数) 里面的 this 指向由调用该方法的 点(.) 前的某个子对象 (实例) 来决定。


class继承


第一步

1、先创建一个额外的新的父类型,类名能够概括两种子类型的特征。
2、两种子类型间相同的属性结构,统一定义到父类型的构造函数中。
3、两种子类型间相同的方法,统一定义到父类型的原型对象中。

class Enemy {
	constructor(x, y) {
		this.x = x;
		this.y = y;
	};
	
	fly() {
		console.log(`飞行到 x: ${this.x},y: ${this.y}的位置。`);
	};
};

第二步

1、让子类型继承父类型: class 子类型 extends 父类型 { }。其实就是用 extends 关键词代替 Object.setPrototypeOf()。
2、在子类型构造函数中,用 super 调用父类型的构造函数,执行父类型构造函数中的语句,为子对象添加公共的属性。super 是子类型中,专门指向父类型中构造函数的关键词。

// 子类 Plane 通过 extends 继承父类 Enemy
// extends: 扩展
class Plane extends Enemy {
	constructor(x, y, score) {
		// this.x = x;
		// this.y = y;
		// 子类通过 super 可以拿到父类中的属性和方法
		super(x, y);
		this.score = score;
 	};
 	
	// fly() {
		//     console.log('飞行');
	// };
	getScore() {
		console.log(`击落一架敌机,得${this.score}分。`);
	};
};

var plane = new Plane(100, 50, 5);
console.log(plane); // {x: 100, y: 50, score: 5}
plane.fly(); // 飞行到 x: 100,y: 50的位置。
plane.getScore(); // 击落一架敌机,得5分。

// 子类 Parachute 通过 extends 继承父类 Enemy
// extends: 扩展
class Parachute extends Enemy {
	constructor(x, y, award) {
		// this.x = x;
		// this.y = y;
		// 子类通过 super 可以拿到父类中的属性和方法
		super(x, y);
		this.award = award;
	};
	
	// fly() {
		//     console.log('飞行');
	// };
	getAward() {
		console.log(`击落一把降落伞,得${this.award}分。`);
	};
};

var parachute = new Parachute(100, 100, 30);
console.log(parachute); // {x: 100, y: 100, award: 30}
parachute.fly(); // 飞行到 x: 100,y: 100的位置。
parachute.getAward(); // 击落一把降落伞,得30分。

构造函数继承-1

1、父函数

function parentClass() {
	this.parentVal = '父类的基本类型值。';
	this.parentFun = function (data) {
		console.log(this.parentVal);
		// 父类的基本类型值。

		console.log(data);
		// 调用父类方法出入的值。
	
		return '父类方法返回的值。';
	};
};

2、父函数原型上的方法

parentClass.prototype.parentProtyFun = function (data) {
	console.log(this.parentVal);
	// 父类的基本类型值。

	console.log(data);
	// 调用原型方法传入的值。

	return '原型方法返回的值。';
};

3、子函数

function student(name) {
	console.log(name); // new 时传入的值。
	parentClass.apply(this, arguments);
	this.name = name;
};

4、实现继承

// 这种方式只能继承父类构造函数中的属性和方法,
// 对于原型对象上的方法无法继承。

// 构造函数继承是利用 call 或者 apply 方法,
// 将父类构造函数的 this 绑定到子类构造函数的 this 上即可。
let example = new student('new 时传入的值。');

console.log(example);
// student {parentVal: "父类的基本类型值。", name: "new 时传入的值。", parentFun: ƒ}

console.log(example.parentVal);
// 父类的基本类型值。

console.log(example.parentFun('调用父类方法出入的值。'));
// 父类方法返回的值。

console.log(parentClass.prototype);
// {parentProtyFun: ƒ, constructor: ƒ}

console.log(example.parentProtyFun('原型方法'));
// example.parentProtyFun is not a function

5、参考的原文链接
知乎-原文


JavaScript数据类型有哪些?如何存储值?

1、数据类型

JavaScript共有8种数据类型。
7种基本数据类型:NullUndefinedBooleanNumberStringSymbol(ES6新增,表示独一无二的值)和BigInt(ES10新增)。
1种引用数据类型:Object。Object里面包含ObjectArrayFunctionDateRegExp等。
总结:JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述8种数据类型之一。


2、存储方式
2.1、原始数据类型

直接存储在栈(stack)中,占据空间小且大小固定,属于被频繁使用的数据,所以放入栈中存储。


2.2、引用数据类型

同时存储在栈(stack)和堆(heap)中,占据空间大,且大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。


null和undefined的区别是什么?

1、null
1.1、用来初始化一个变量,这个变量可能赋值为一个对象。

var a = null;
console.log(a); 
// null
let b = { a: null };
console.log(b); 
// {a: null}
const c = [null];
console.log(c); 
// [null]

1.2、用来和一个已经初始化的变量比较,这个变量可以是对象,也可以不是对象。

var a = 10;
console.log(a == null); 
// false
var b = null;
console.log(b == null); 
// false
var c = {};
console.log(c == null); 
// false
var d = [];
console.log(d == null); 
// false

1.3、当函数的参数期望是对象时,用作参数的传入。

function fn(a) {
	console.log(a); 
	// null
	console.log(typeof a); 
	// object
};
fn(null);

1.4、当函数的返回值期望是对象时,用作返回值传出。

function fn(a) {
	console.log(a); 
	// 1
	return null;
};
console.log(fn(1)); 
// null
console.log(typeof fn(1)); 
// object

2、undefined
2.1、声明一个变量,但是没有赋值。

var vlcs;
console.log(vlcs); 
// undefined

2.2、访问对象上不存在的属性或者未定义的变量。

console.log(Object.foo); 
// undefined
console.log(typeof demo); 
// undefined

2.3、函数定义了形参,但没有传递实参。

// 定义形参 a
function fn(a) {
	console.log(a); 
	// undefined
};
//未传递实参
fn(); 

2.4、使用void对表达式求值。

void 0; 
// undefined
void false; 
// undefined
void []; 
// undefined
void null; 
// undefined
void function fn() {}; 
// undefined

3、总结

3.1、undefined表示一个变量最原始的自然状态值。
3.2、null表示一个变量被人为的设置为空对象, 而不是原始状态。
3.3、在实际使用过程中,为了保证变量所代表的语义,不要对一个变量赋值为undefined,当需要释放一个对象时,直接赋值为null即可。


图片懒加载

1、css

.img_box {
	margin-top: 30px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	border: 1px solid red;
}
        
.img_box>img {
	width: 450px;
	height: 500px;
	margin: 5px 0;
}

2、html

<div class="img_box">
	<!-- 加载 loading 图片是在 html 部分实现 -->
	<!-- src 存放的是伪图片,等待图片 -->
	<!-- data-src 是自定属性,存放真是的图片地址 -->
	<img src="/img/loading02.gif" data-src="/img/01.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/02.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/03.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/04.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/05.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/06.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/07.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/08.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/09.jpg" alt="">
</div>

3、JavaScript

// onload 是等所有的资源文件加载
// 完毕以后再绑定事件
window.onload = function() {
	// 1、获取一面图片标签元素
	// 获取图片列表,
	// 即 img 标签列表
	let imgs = document.querySelectorAll('img');

	// 2、获取到浏览器顶部的距离
	function getTop(e) {
		return e.offsetTop;
	}

	// 3、懒加载实现
	function lazyload(dataImg) {
		// 3.1、获取可视区域高度
		let innerHeight = window.innerHeight;
		// 3.2、获取滚动区域高度
		let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

		for (let i = 0; i < dataImg.length; i++) {
			// 3.3、如果图片距离顶部的距离大于
			// 可视区域和滚动区域之和时触发懒加载
			if ((innerHeight + scrollTop) > getTop(dataImg[i])) {
				// 3.4、真实情况是页面开始有 1 秒空白,
				// 所以使用 setTimeout 定时 1s
				(function(i) {
					setTimeout(function() {
						// 3.5、不加立即执行函数 i 会等于 9
						// 隐形加载图片或其他资源,
						// 创建一个临时图片,
						// 这个图片在内存中不会到页面上去。
						// 实现隐形加载
						let temp = new Image();
						// console.log('new:', temp);

						// 3.6、只会请求一次
						temp.src = dataImg[i].getAttribute('data-src');
						// console.log('src:', temp);

						// 3.7、onload 判断图片加载完毕,
						// 真实图片加载完毕,
						// 再赋值给 dom 节点
						temp.onload = function() {
							// 3.8、获取自定义属性 data-src,
							// 用真图片替换假图片
							dataImg[i].src = dataImg[i].getAttribute('data-src');
							// console.log('dataImg:', dataImg[i].src);
						};
					}, 1000);
				})(i);
			}
		}
	}

	// 4、调用懒加载函数,
	// 传递图片标签元素
	lazyload(imgs);

	// 5、滚动监听
	window.onscroll = function() {
		// 调用懒加载函数
		lazyload(imgs);
	};
}

JavaScript之对象 (object) 解构

1、定义一个普通对象

let objectData = {
	Uname: '李清照',
	age: '20',
	interest: {
		writeCode: '写代码',
		readBook: '看书'
	},
	introduce: function() {
		console.log(`我叫${Uname},今年${age}。喜欢${writeCode}${readBook}`);
	}
};

2、解构重命名

// Uname: newUname 的意思是: 取到 Uname 的属性值,
// 冒号后面的变量名没有花括号,
// 如果有花括号则可以继续解构。
// 意思是把取到的值交给冒号后面的变量名,
// 相当于把 Uname 变量重命名为 newUname
let {
	Uname: newUname, 
	age: newAge, 
	interest: {
		writeCode: newWriteCode,
		readBook: newReadBook
	},
	introduce: newIntroduce
} = objectData;

console.log(newUname); // 李清照
console.log(newAge); // 20
console.log(newWriteCode); // 写代码
console.log(newReadBook); // 看书
newIntroduce(); // 我叫李清照,今年20。喜欢写代码和看书。

3、直接使用,不需重新命名

let {
	Uname, 
	age, 
	interest: {
		writeCode,
		readBook
	},
	introduce
} = objectData;

console.log(Uname); // 李清照
console.log(age); // 20
console.log(writeCode); // 写代码
console.log(readBook); // 看书
introduce(); // 我叫李清照,今年20。喜欢写代码和看书。

检测一个对象是不是数组

1、基本数据类型

var number = 100;
var string = 'asdfghjkl';
var boolean = true;
var nu = null;
var un = undefined;

2、引用数据类型

var fun = function() {};
var object = {};
var array = [3, 6, 9];
var date = new Date();

3、typeof()或者typeof,因为typeof有局限性,所以不能用来做数组类型的检测

// typeof()的局限性
console.log(typeof(number)); 
// number
console.log(typeof string); 
// string
console.log(typeof(boolean)); 
// boolean

// 因为null的意思是指向一个空地址
// 所以更像一个没有意义的空对象
console.log(typeof(nu)); 
// object
console.log(typeof un); 
// undefined

// typeof可以检测函数
console.log(typeof(fun)); 
// function

// typeof不能检测对象的具体类型
// 把除了Function对象以外的引用类型对象都检测为object
console.log(typeof object); 
// object
console.log(typeof array); 
// object
console.log(typeof(date)); 
// object

4、proto

console.log(object.__proto__ == Array.prototype); 
// false
console.log(array.__proto__ == Array.prototype); 
// true
console.log(date.__proto__ == Array.prototype); 
// false

5、Object.getPrototypeOf([value])

// __proto__可能被浏览器禁用,所以有等效的函数检测。
console.log(Object.getPrototypeOf(object) == Array.prototype); 
// false
console.log(Object.getPrototypeOf(array) == Array.prototype); 
// true
console.log(Object.getPrototypeOf(date) == Array.prototype); 
// false

6、Array.prototype.isPrototypeOf([value])

console.log(Array.prototype.isPrototypeOf(object)); 
// false
console.log(Array.prototype.isPrototypeOf(array)); 
// true
console.log(Array.prototype.isPrototypeOf(date)); 
// false

7、使用构造函数constructor进行检测,即使用父级原型对象中的constructor属性

console.log(object.constructor == Array); 
// false
console.log(array.constructor == Array); 
// true
console.log(date.constructor == Array); 
// false

8、[value] instanceof Array

console.log(object instanceof Array); 
// false
console.log(array instanceof Array); 
// true
console.log(date instanceof Array); 
// false

9、万能方法,不存在兼容性:[value].prototype.toString().call()

console.log(Object.prototype.toString.call(object)); 
// [object Object]
console.log(Object.prototype.toString.call(array)); 
// [object Array]
console.log(Object.prototype.toString.call(date)); 
// [object Date]

10、isArray():ES5新增,存在兼容性问题

console.log(Array.isArray(object)); 
// => false
console.log(Array.isArray(array)); 
// => true
console.log(Array.isArray(date)); 
// => false

11、以上方法需要了解面向对象、原型prototype和原型链

在这里插入图片描述


JavaScript之new操作符

1、new操作符做的事情-01

1. 创建了一个全新的对象。
2. 将对象链接到这个函数的prototype对象上。
3. 执行构造函数,并将this绑定到新创建的对象上。
4. 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。


2、new操作符做的事情-02

1. 创建一个全新的对象 (无原型的Object.create(null)) 。目的是保存new出来的实例的所有属性。
2. 将构造函数的原型赋值给新创建的对象的原型。目的是将构造函数原型上的属性继承下来。
3. 调用构造函数,并将this指向新建的对象。目的是让构造函数内的属性全部转交到该对象上,使得this指向改变,方法有三 : apply、call、bind 。
4. 判断构造函数调用的方式,如果是new的调用方式,则返回经过加工后的新对象,如果是普通调用方式,则直接返回构造函数调用时的返回值。


3、代码

function myNew(Constructor, ...args) {
	// 判断Constructor参数是否是函数
	if (typeof Constructor !== 'function') return 'Constructor.apply is not a function';

	// 1、创建了一个全新的对象。
	let newObject = {};

	// 2、将对象链接到这个函数的prototype对象上。
	newObject.__proto__ = Constructor.prototype;

	// 此处是把 1 / 2 步结合到一起
	// const newObject = Object.create(Constructor.prototype);

	// 3、执行构造函数,
	// 并将this绑定到新创建的对象上。
	let result = Constructor.apply(newObject, args);

	// 4. 判断构造函数执行返回的结果是否是引用数据类型,
	// 若是则返回构造函数执行的结果,
	// 否则返回创建的对象。
	if ((result !== null && typeof result === 'object') || (typeof result === 'function')) return result;
	return newObject;
};

// 需要被new的函数
function NewTest(args) {
	this.dataValue = args;
	return this;
};

// 定义参数
let dataObj = {
	sname: '杨万里',
	number: 3,
	web: 'vue'
};
let dataArray = [5, 'uniApp', '范仲淹'];

// 执行myNew函数
let test = myNew(NewTest, 1, 'JavaScript', '辛弃疾', dataObj, dataArray);
console.log(test); // NewTest {dataValue: Array(5)}

4、new的原理是什么

new操作符做的事情。


5、通过new的方式创建对象和通过字面量创建对象有什么区别

对象都是通过new产生。function Foo() {},function是语法糖,内部等同于new Function() 。
let object = { number: 1 } ,使用字面量创建对象,内部也是使用了new Object() 。
对于创建一个对象来说,更推荐使用字面量的方式创建对象。因为使用new Object()的方式创建对象,需要通过作用域链一层层找到Object ,如果使用字面量的方式就没有这个问题。


原型实例继承-2

1、父函数

function parentClass() {
	this.parentVal = '父类的基本类型值。';
	
	this.parentFun = function (data) {
		console.log(this.parentVal);
		// 父类的基本类型值。

		console.log(data);
		// 调用父类方法出入的值。

		return '父类方法返回的值。';
	};
};

2、父函数原型上的方法

parentClass.prototype.parentProtyFun = function (data) {
	console.log(this.parentVal);
	// 父类的基本类型值。

	console.log(data);
	// 调用原型方法传入的值。

	return '原型方法返回的值。';
};

3、子函数

function student(name) {
	this.name = name;
	console.log(name); // new 时传入的值。
	parentClass.apply(this, arguments);
};

4、实现继承

// 这是比较常用的一种实现继承的方式。
// 1、将 student 的 prototype 对象指向 parentClass 的一个实例。
// 此操作完全删除了 student.prototype 对象原本的内容,
// 然后赋予给它一个新的值。
student.prototype = new parentClass();

// 2、任何一个构造函数都有一个 prototype 对象,
// 这个 prototype 对象都有一个 constructor 属性指向自身的构造函数。
// 2.1、因为第一行对 prototype 对象进行了重新赋值,
// 所以 prototype 对象的 constructor 属性也发生了改变,
// 变成指向 parentClass ,
// 所以必须手动将 student.prototype.constructor 指回 student 。
// 2.2、如果没有(student.prototype.construct = student) 这行代码,
// 则 student.prototype.constructor == parentClass 和
// example.constructor == parentClass 的结果返回 false 。
// 2.3、这里比较好理解,因为 example 是 student 的实例化对象,
// 它的 constructor 属性默认继承自 parentClass.prototype ,
// 而 parentClass.prototype 的 constructor 属性继承自 parentClass.prototype ,
// 最后找到 parentClass.prototype.constructor 指向 parentClass 。
// 显然如果没有这句话,将会导致继承链的错乱。
// 注意:在编程时,如果对 prototype 对象进行重新赋值后,
// func.prototype = { sname: '李白' };
// 记得手动奖 prototype 的 constructor 属性智慧原来的构造函数。
// func.prototype.constructor = func;
student.prototype.construct = student;

let example = new student('new 时传入的值。');

console.log(example);
// {parentVal: "父类的基本类型值。", name: "new 时传入的值。", parentFun: ƒ}

console.log(example.parentVal);
// 父类的基本类型值。

console.log(example.parentFun('调用父类方法出入的值。'));
// 父类方法返回的值。

console.log(example.parentProtyFun('调用原型方法传入的值。'));
// 原型方法返回的值。

5、参考的原文链接
知乎-原文


原型直接继承-3

1、父函数

function parentClass() {
	this.parentVal = '父类的基本类型值。';
	this.parentFun = function (data) {
		console.log(this.parentVal);
		// 父类的基本类型值。

		console.log(data);
		// 调用父类方法出入的值。

		return '父类方法返回的值。';
	};
};

2、父函数原型上的方法

parentClass.prototype.parentProtyFun = function (data) {
	console.log(this.parentVal);
	// 父类的基本类型值。

	console.log(data);
	// 调用原型方法传入的值。

	return '原型方法返回的值。';
};

3、子函数

function student(name) {
	this.name = name;
	console.log(name); // new 时传入的值。
	parentClass.apply(this, arguments);
};

4、实现继承

// 原型直接继承是通过直接将子类构造函数的原型对象,
// 直接赋值为父类构造函数的原型对象,
// 从而达到继承的目的。
student.prototype = parentClass.prototype;
student.prototype.constructor = student;

// new 实例
let example = new student('new 时传入的值。');

console.log(example);
// student {parentVal: "父类的基本类型值。", name: "new 时传入的值。", parentFun: ƒ}

console.log(example.parentVal);
// 父类的基本类型值。

console.log(example.parentFun('调用父类方法出入的值。'));
// 父类方法返回的值。

console.log(example.parentProtyFun('调用原型方法传入的值。'));
// 原型方法返回的值。

5、参考的原文链接
知乎-原文


ES6的Class继承、super、extends、constructor-4

1、父类

class parent {
	constructor(name) {
		this.sname = name;
	};

	parentFunc(data) {
		console.log(this.sname);
		// new 时传入的值。

		console.log(data);
		// 调用父类方法传入的值。
		
		return '父类方法的返回值。';
	};
};

2、子类(直接实现继承)

class children extends parent {
	constructor(name) {
		super(name);
		this.sname = name;
	};
	
	childrenFunc(data) {
		console.log(this.sname);
		// new 时传入的值。

		console.log(data);
		// 调用子类方法传入的值。

		return '子类方法的返回值。';
	};
};

3、使用

// new 实例
let example = new children('new 时传入的值。');

console.log(example);
// new 时传入的值。

console.log(example.sname);
// new 时传入的值。

console.log(example.parentFunc('调用父类方法传入的值。'));
// 父类方法的返回值。

console.log(example.childrenFunc('调用子类方法传入的值。'));
// 子类方法的返回值。

4、参考的原文链接
知乎-原文


作用域-作用域链

1、作用域
1.1、定义

作用域是指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。


1.2、全局作用域

一般指的是widow。


1.3、函数作用域

定义在函数中的变量就在函数作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。


1.4、块级作用域

???


1.5、词法作用域

词法作用域是指一个变量的可见性,及其文本表述的模拟值。


1.6、动态作用域

???


2、作用域链
定义

查找变量的时候,先从当前上下文的变量对象中查找,如果没有找到,就向父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象(全局对象),如果全局上下文对象中也没有找到变量,则返回undefined。这样由多个执行上下文的变量对象构成的链表就是作用域链。


3、参考文章
谈谈JavaScript的作用域


原型与原型链-总结

1、概念

在JavaScript中,对象都有__proto__属性(隐式原型),指向构造该对象的构造函数的原型,而函数Function比较特殊,它除了和其他对象一样有__proto__属性外,还有自己特有的属性prototype称之为原型对象,原型对象有一个constructor属性,该属性指回该函数本身。


2、关键字介绍

1、每个函数都会有prototype属性,普通对象没有prototype属性。prototype是构造函数的原型对象。
2、每个对象都有双下划线__proto__属性,因为函数也是对象,所以函数也有双下划线__proto__属性。它指向构造函数的原型对象。
3、constructor是原型对象上的一个指向构造函数的属性。


原型实例继承和原型直接继承的区别?

1、原型实例继承

student.prototype = new parentClass();
student.prototype.construct = student;

2、原型直接继承

student.prototype = parentClass.prototype;
student.prototype.constructor = student;

渲染(绑定)数据的时候嵌套两层(需要点(.)两次)的时候报错的解决方案

1、错误写法

<div>{{item.obj.name}}</div>

2、正确写法

<div>{{(item.obj || {}).name}}</div>

JavaScript两种解决跨域的方法

1、定义

1.1、浏览器同源(origin)策略:浏览器规定发送ajax请求时,只有相同域名的客户端和相同域名的服务端才能发送请求。
1.2、同源:.html在哪台服务器请求,数据也需要在哪台服务器请求。
1.3、报错:Access-Control-Allow-Origin不允许跨源头发送请求。
1.4、同源策略的本质:可以发送ajax请求,可以正常执行服务端的程序,也可以顺利返回正确的结果,但是,浏览器经过检查数据的来源,如果和当前网页的来源不一致,浏览器禁止使用此数据。


2、跨域的四种情况
2.1、域名不同

  • www.a.com
  • www.b.com

2.2、端口号不同

  • www.a.com:3000
  • www.a.com:5500

2.3、协议不同

  • http://www.a.com
  • https://www.a.com

2.4、域名和地址相互请求

  • http://localhost
  • http://127.0.0.1

3、可以跨域的标签

3.1、img
3.2、link
3.3、iframe
3.4、script


4、CORS(cross origin resource share)跨域

不要使用res.send(result); 这句话包含了下面三句话,属于下面三句的简写,重写这三句话。

res.writeHead(localhost:3000); // 写信封封面
res.write(); // 写信的内容
res.end(); // 发送
res.writeHead(200, {
	// 只针对某个地址实现跨域
	// 'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
	// 所有请求都允许跨域
	'Access-Control-Allow-Origin': '*',
	'Content-Type': 'application/json;charset=utf-8'
});
res.write(JSON.stringify([value]));
res.end();

5、JSONP(json with padding)跨域

这里的 padding 是填充。


方案一
客户端

<script src='http://127.0.0.1:8080'></script>

服务端

let weather = '晴 22-30℃';
let string = `document.write(${weather})`;
res.write(string);
res.end();
// 问题:服务端将客户端执行的程序固定了,
// 众口难调。

方案二
客户端

<script>
	fonctiong show(data) {
		// alert(data);
		document.write(data);
	};
</script>
<script src='http://127.0.0.1:8080'></script>

服务端

let weather = '晴 22-30℃';
let string = `show(${weather})`;
res.write(string);
res.end();
// 问题:服务端将函数名固定了,
// 众口难调。

方案三(请求时携带参数)
客户端

<script>
	fonctiong show(data) {
		// alert(data);
		document.write(data);
	};
</script>
<script src='http://127.0.0.1:8080?fname=show'></script>

服务端

const http = require('http');
const url = require('url');
http.createServer((req, res)=>{
	// 不使用 express 框架
	let fname = url.parse(req.url, true).query.fname;
	let weather = '晴 22-30℃';
	let string = `${fname}(${weather})`;
	res.write(string);
	res.end();
});
// 问题:客户端的 <script> 标签固定了。
// 需要动态点击请求时无法实现。

方案四(请求时携带参数)
客户端

<button></button>

<script>
	$('button').click(function() {
		// 注意:不要拷贝结束标签
		$(`<scriptsrc="http://localhost:8080">`).appendTo('body');
	});
	fonctiong show(data) {
		// alert(data);
		document.write(data);
		// 找到最后一个 <script> 删除掉
		// 因为每次点击都会添加一个 <script>
		$('body>script:last').remove();
	};
</script>
<script src='http://127.0.0.1:8080?fname=show'></script>

服务端

const http = require('http');
const url = require('url');
http.createServer((req, res)=>{
	// 不使用 express 框架
	let fname = url.parse(req.url, true).query.fname;
	let weather = '晴 22-30℃';
	let string = `${fname}(${weather})`;
	res.write(string);
	res.end();
});

方案五
客户端

<button></button>

<script>
	$('button').click(function() {
		$.ajax({
			url:'http://127.0.0.1:8080',
			type: 'GET',
			// data: '',
			dataType: 'jsonp',
			success: function(result) {
				alert(result);
			}
		});
	});
</script>

服务端

const http = require('http');
const url = require('url');
http.createServer((req, res)=>{
	// 不使用express框架
	// ajax自带callback
	let fname = url.parse(req.url, true).query.callback;
	let weather = '晴 22-30℃';
	let string = `${fname}(${weather})`;
	res.write(string);
	res.end();
});

JavaScript之回调函数

1、基础回调

function callback(task) {
	console.log('主函数做自己的事情。');
	task();
};

callback(function() {
	console.log('回调函数被执行。');
});  

2、replace的回调

// 使用 replace 实现回调,
// 把首字母替换成大写
var str = 'I miss you';
str = str.replace(/\b[a-z]/ig, function(keyW) {
	return keyW.toUpperCase();
});

console.log(str); // I Miss You

3、总结

传入主程序(主函数)的参数是一个函数,并且在主函数中调用传入的函数。


JavaScript之单词首字母转大写

1、代码

function toUpperCase(value) {
	return value.replace(/\b[a-zA-Z]+\b/g, item => {
		return item.charAt(0).toUpperCase() + item.substring(1);
	});
};

console.log(toUpperCase('I miss you')); // I Miss You

JavaScript之字符串方法与对象

1、概要定义

序号方法说明result
1length字符串长度返回字符串的长度
2indexOf()查找字符串中的字符串返回字符串中指定文本首次出现的索引(位置)
3search()检索字符串中的字符串搜索特定值的字符串,并返回匹配的位置
4slice()****提取字符串的某个部分并在新字符串中返回被提取的部分
5substring()****类似于 slice()。不同之处在于 substring() 无法接受负索引
6substr()****类似于 slice()。不同之处在于第二个参数规定被提取部分的长度
7replace()替换字符串内容用另一个值替换在字符串中指定的值
8toUpperCase()转换为大写把字符串转换为大写
9toLowerCase()转换为小写把字符串转换为小写
10concat()****连接两个或多个字符串
11trim()String.trim()删除字符串两端的空白符
12charAt()****返回字符串中指定下标(位置)的字符串
13charCodeAt()****返回字符串中指定索引的字符 unicode 编码
14split()****把字符串转换为数组
15lastIndexOf()****返回指定文本在字符串中最后一次出现的索引

2、相关连接

2.1、JS 字符串方法
2.2、JS Sting


JavaScript 中的执行上下文和执行栈

1、执行上下文
定义

简而言之,执行上下文是评估和执行JavaScript代码的环境的抽象概念。每当Javascript代码在运行的时候,它都是在执行上下文中运行。


执行上下文的类型

1、全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的window对象(浏览器的情况下),并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
2、函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
3、Eval函数执行上下文 — 执行在eval函数内部的代码也会有它属于自己的执行上下文,但由于JavaScript开发者并不经常使用eval,所以在这里我不会讨论它。


2、执行栈
定义

1、执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
2、当JavaScript引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
3、引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。


JavaScript实现添加班级和离开班级功能、通知、加入、删除、push、indexOf、splice

function myClass() {
    let students = [],
        operations = {
            join: (name) => {
                students.push(name);
                return `${name},加入了班级,当前班级总人数${students.length}人,分别是:${students.toString()}`;
            },
            leave: function (name) {
                // 共用属性
                let str = '';

                // 方式一
                // for (let i = 0; i < students.length; i++) {
                //     if (students[i] !== name) return `${name},未找到`;

                //     str += `${students[i]},离开了班级,`;
                //     students.splice(i, 1);
                //     str += `当前班级总人数${students.length}人,分别是:${students.toString()}`;

                //     return str;
                // }

                // 方式二
                let index = students.indexOf(name);

                if (index == -1) return `${name},未找到`;

                str += `${students[index]},离开了班级,`;
                students.splice(index, 1);
                str += `当前班级总人数${students.length}人,分别是:${students.toString()}`;

                return str;
            }
        };
    return operations;
}

let obj = myClass();
console.log(obj.join('舒冬'));
// 舒冬,加入了班级,当前班级总人数1人,分别是:舒冬
console.log(obj.join('半晨'));
// 半晨,加入了班级,当前班级总人数2人,分别是:舒冬,半晨
console.log(obj.join('阮一峰'));
// 阮一峰,加入了班级,当前班级总人数3人,分别是:舒冬,半晨,阮一峰
console.log(obj.leave('舒冬'));
// 舒冬,离开了班级,当前班级总人数2人,分别是:半晨,阮一峰
console.log(obj.leave('静慧'));
// 静慧,未找到

DOM节点

1、方法

方法描述/作用使用备注
getElementById()返回带有指定 ID 的元素。
getElementsByTagName()返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)。
getElementsByClassName()返回包含带有指定类名的所有元素的节点列表。
appendChild()把新的子节点添加到指定节点。
removeChild()删除子节点。
remove()移除自身节点。
replaceChild()替换子节点。
insertBefore()在指定的子节点前面插入新的子节点。
createAttribute()创建属性节点。
createElement()创建元素节点。
createTextNode()创建文本节点。
getAttribute()返回指定的属性值。
setAttribute()把指定属性设置或修改为指定的值。

W3school - 属性


2、属性

属性描述/作用使用备注
innerHTML获取元素内容的最简单方法是使用 innerHTML 属性。
nodeName属性规定节点的名称。
nodeValue属性规定节点的值
nodeType属性返回节点的类型。nodeType 是只读的。
offsetHeight获取元素高度

W3school - 属性


3、设置 style 属性
3.1、直接设置 style 对象

1. el.style.color = '#b50029;'
2. el.style.fontSize = '30px;'
3. el.style['font-size'] = '30px;'


3.2、设置 style 属性

1. el.setAttribute('style', 'color: red;')
2. el.setAttribute('style', 'font-size: 30px;')
3. el.setAttribute('style', 'font-size: 30px;')
4. el.setAttribute('style', 'color: red;' + 'font-size: 30px;' + 'background-color: green;')


3.3、设置 cssText

1. el.style.cssText = "color: red; font-size: 30px;"
2. el.style.cssText += 'background-color: yellow;'
3. el.style.cssText = "color: red; font-size: 30px;"
4. el.style.cssText = "text-decoration: line-through;"


3.4、相关链接

博客园


JavaScript之函数作用域(scopes)和函数作用域链(scope chain)

示例1

var a = 10;
function funScopes() {
	// 这里获取的是函数里面的 a
	console.log(a); // undefined
	// 这里存在变量提升
	var a = 20;
};
funScopes();
// 这里获取的是全局的 a
console.log(a); // 10

示例2

var a = 10;

function funScopes() {
	// 获取的是全局 a
	console.log(a); 
	// 10
	// 给全局 a 重新赋值
	a = 20;
}

funScopes();
// 获取的是改变值之后的全局 a
console.log(a); 
// 20

示例3

var a = 10;

function funScopes(a) {
	// 形参变量会自动在函数中 var a;   
	// 获取的是传进来的值 
	console.log(a); 
	// 10
	// 给当前函数中的 a 重新赋值
	a = 20;
}

funScopes(a);
// 获取的是全局中的 a
console.log(a); 
// 10

示例4

var a = 10;

function funScopes() {
	// 函数作用域一开始就已经定义好,
	// 跟函数在哪里调用无关。
	// 所以这里的 a 是全局的
	console.log(a); 
	// 10
}

(function() {
	var a = 100;
	
	funScopes();
})();

html之标签元素设置自定义属性、setAttribute、getAttribute

第一种方法

// 设置
btnList[i].myIndex = i;

// 获取
this.myIndex;

第二种方法

// 设置
btnList[i].setAttribute('data-index', i);

// 获取
this.getAttribute('data-index');

JavaScript之对象打点的访问方式

功能

可以在dataObj对象中,寻找用连续点符号的keyName属性。


代码

// export default function lookup(dataObj, keyName) {
function lookup(dataObj, keyName) {
	// 看看keyName中有没有点符号,但是不能是.本身
    if (keyName.indexOf('.') != -1 && keyName != '.') {
        // 如果有点符号,那么拆开成数组
        var keys = keyName.split('.');
        // 设置一个临时变量,这个临时变量用于周转,一层一层找下去。
        var temp = dataObj;
        
        // 每找一层,就把它设置为新的临时变量
        for (let i = 0; i < keys.length; i++) temp = temp[keys[i]];
        
        return temp;
    }
    
    // 如果这里面没有点符号
    return dataObj[keyName];
}

let objData = {
	a: {
		b: {
			c: 100
		},
		d: {
			e: 1000
		}
	},
	f: 10000,
	g: {
		h: 100000
	}
};

console.log(lookup(objData, 'a.b.c')); 
// 100
console.log(lookup(objData, 'a.d.e')); 
// 1000
console.log(lookup(objData, 'f')); 
// 10000
console.log(lookup(objData, 'g.h')); 
// 100000

ES6模块化

1、默认导出/导入
1.1、默认导出

let n1 = 10;
let n2 = 20;
function show() {};

export default {
	n1,
	show
};

// 一个.js文件中不能有两个
// export default { };

// export default {
//	n2,
// };

1.2、默认导入

import m1 from './01.默认导出.js';

console.log(m1); 
// { n1: 10, show: [Function: show] }

2、按需导出/导入
2.1、按需导出

export let s1 = 'aaa';
export let s2 = 'ccc';
export function say() {};

export default {
	a: 20
};

2.2、按需导入

import info, { s1, s2 as str2, say } from './03.按需导出.js';
// as: 重命名 把 s2 重命名为 str2
console.log(s1); // aaa
console.log(str2); // ccc
console.log(say); // [Function: say]
console.log(info); // { a: 20 }

3、直接运行模块中的代码
3.1、需要运行的代码文件

for (let i = 0; i < 3; i++) {
	console.log(i);
	// 0
	// 1
	// 2
}

3.2、运行代码的文件

import './05.直接运行模块中的代码.js';

JavaScript实现数值转汉字大写、价格

1、代码

<input type="text" name="je" onkeyup="priceInput(this)" />
<div id="showVal"></div>

function priceInput(obj) {
	document.getElementById('showVal').innerText = this.toChineseMoney(obj.value);
}

function toChineseMoney(n) {
	if (!/^(0|[1-9]\d*)(\.\d+)?$/.test(n)) return "数据非法";

	let unit = "仟佰拾亿仟佰拾万仟佰拾圆角分",
		str = "";
		n += "00";
	let p = n.indexOf('.');
	
	if (p >= 0) n = n.substring(0, p) + n.substr(p + 1, 2);

	unit = unit.substr(unit.length - n.length);
	for (let i = 0; i < n.length; i++) str += '零壹贰叁肆伍陆柒捌玖'.charAt(n.charAt(i)) + unit.charAt(i);

	return str.replace(/零(仟|佰|拾|角)/g, "零").replace(/(零)+/g, "零").replace(/零(万|亿|圆)/g, "$1").replace(/(亿)万|壹(拾)/g, "$1$2").replace(/^圆零?|零分/g, "").replace(/圆$/g, "圆整");
}

2、相关链接
2.1、CSDN-前端js价格转换为大写的
2.2、博客园-js转换金额为中文大写
2.3、博客园-js金额转换为大写


JavaScript实现价格输入控制并翻译为中文大写、结合

1、HTML部分

<div>
	<input type="text" maxlength="12" name="je" placeholder="请输入价格" onkeyup="priceInput(this)" />
	<div id="showVal"></div>
</div>

<script src="./index.js"></script>

2、JavaScript部分

function priceInput(obj) {
	// 清除数字和点以外的字符
	obj.value = obj.value.replace(/[^\d.]/g, "");
	// 验证第一个字符是否是数字,也就是是第一个字符不能是点
	obj.value = obj.value.replace(/^\./g, "");
	// 只保留第一个点, 清除多余的点
	obj.value = obj.value.replace(/\.{2,}/g, ".");
	obj.value = obj.value.replace(".", "$#$").replace(/\./g, "").replace("$#$", ".");
	// 只能输入两个小数
	obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');

	// 此处控制的是如果没有小数点,首位不能为0,类似于01、02的金额
	if (obj.value.indexOf(".") < 0 && obj.value != "") {
		if (obj.value.substr(0, 1) == '0' && obj.value.length == 2) {
			obj.value = parseFloat(obj.value);
		}
	} else { // 此处控制的是如果有小数点,整数如果大于等于两位,第一位不能为0,类似于01.16、02.77的金额
		let val = obj.value,
			beforePoint = val.split('.')[0],
			afterPoint = val.split('.')[1];
		if (beforePoint.length >= 2 && beforePoint[0] == 0) {
			obj.value = `${beforePoint.substring(1)}.${afterPoint}`;
		}
	}

	document.getElementById('showVal').innerText = this.toChineseMoney(obj.value);
}

function toChineseMoney(n) {
	let unit = "仟佰拾亿仟佰拾万仟佰拾圆角分",
		str = "";
		n += "00";
	let p = n.indexOf('.');

	if (p >= 0) n = n.substring(0, p) + n.substr(p + 1, 2);

	unit = unit.substr(unit.length - n.length);
	for (let i = 0; i < n.length; i++) str += '零壹贰叁肆伍陆柒捌玖'.charAt(n.charAt(i)) + unit.charAt(i);

	return str.replace(/零(仟|佰|拾|角)/g, "零").replace(/(零)+/g, "零").replace(/零(万|亿|圆)/g, "$1").replace(/(亿)万|壹(拾)/g, "$1$2").replace(/^圆零?|零分/g, "").replace(/圆$/g, "圆整");
}

JavaScript之整数翻转、包括负整数、Number、String、substr、substring

function integerFlip(params) {
	// 0不是整数
	// 如果没有此判断
	// 值为0会进入!Number(params)判断
	if (params == 0 || params == '0') return Number(params);
	if (!Number(params)) return '请输入整数!';
	
	// 因为数字没有length属性
	// 所以需要转为字符串
	params = String(params);
	// 从末尾开始遍历字符串
	let i = params.length,
	    // 遍历的结果
	    result = '';
	
	for (; i > 0; i--) result += params[i - 1];
	
	if (result.substr(0) == '0') result = `-${result.substring(1)}`;
	if (result.substr(-1) == '-') result = `-${result.substring(0, result.length - 1)}`;
	if (result.substr(-1) == '+') result = `+${result.substring(0, result.length - 1)}`;
	
	return Number(result);
}

console.log(integerFlip(123));
// 321
console.log(integerFlip(-123));
// -321
console.log(integerFlip(130));
// 31
console.log(integerFlip(-130));
// -31
console.log(integerFlip(+170));
// 71
console.log(integerFlip(0));
// 0
console.log(integerFlip('123柒'));
// 请输入整数!

JavaScript之数据改造、后端只返回有数据的时段,没数据的时段需要前端自己补全、isArray、throw、forEach、hasOwnProperty、call、for in、push

function handleApacheECharts(existenceData) {
	if (!Array.isArray(existenceData)) throw new Error('not an array.');
	if (!existenceData[0].time) throw new Error('data error.');
	
	let obj = {
		1: { time: "08:00-09:00", value: 0, },
		2: { time: "09:00-10:00", value: 0, },
		3: { time: "10:00-11:00", value: 0, },
		4: { time: "11:00-12:00", value: 0, },
		5: { time: "12:00-13:00", value: 0, },
		6: { time: "13:00-14:00", value: 0, },
		7: { time: "14:00-15:00", value: 0, },
		8: { time: "15:00-16:00", value: 0, },
		9: { time: "16:00-17:00", value: 0, },
	},
		arr = {
			time: [],
			value: []
		};
	
	existenceData.forEach(item => {
		for (const key in item) {
			if (Object.hasOwnProperty.call(item, key)) {
				if (key === 'time') obj[item[key] - 7].value = item.value;
			}
		}
	});
	
	for (const j in obj) {
		if (Object.hasOwnProperty.call(obj, j)) {
			let item = obj[j];
			for (const key in item) {
				if (Object.hasOwnProperty.call(item, key)) {
					if (key === "time") {
						arr.time.push(item[key]);
					} else if (key === "value") {
						arr.value.push(item[key]);
					}
				}
			}
		}
	}
	
	return arr;
}

console.log(handleApacheECharts([{ time: '10', value: 30 }, { time: '16', value: 10 }, { time: '16', value: 70 }]));
console.log(handleApacheECharts());
console.log(handleApacheECharts(''));
console.log(handleApacheECharts(1));
console.log(handleApacheECharts({}));
console.log(handleApacheECharts([1, 2]));

JavaScript实现文字转表情包、RegExp、正则、matchAll、createDocumentFragment、append、createElement、append、remove、slic

let str = '[左哼哼]你好[微笑]世界[哈哈]',
    str2 = '你好<div></div>世界';

function scrmTextEmojiRenderFn(value) {
    const reg = /\[+[^\[\]]+\]/g;
    if (value === null || value === undefined || value === '') value = '';

    const fragment = document.createDocumentFragment();
    let renderRes = value,
        renderArr = [...value.matchAll(reg)];

    if (renderArr.length > 0) { // 匹配[]为主的表情
        // 用reg.test(value)做判断,renderArr的少了第一项,不明原因
        let renderResArr = [];
        // 专门处理表情所处的位置,表情的长度
        renderArr.forEach(item => {
            let renderItemTemplate = {
                name: '',
                index: '',
                length: '',
            };
            renderItemTemplate.name = item[0];
            renderItemTemplate.index = item['index'];
            renderItemTemplate.length = renderItemTemplate.name.length;
            renderResArr.push(renderItemTemplate);
        })
        const renderEmojiFn = (name, spanDom, renderRes) => { // 专门处理表情的渲染
            // if (emojisAmap[name]) {// 字符串携带有[]
            //     spanDom.classList.add('emoji_a');
            //     spanDom.classList.add(emojisAmap[name]);
            // } else {
            spanDom.classList.remove('chat-emoji');
            spanDom.innerText = "[" + name + "]";
            // }
            renderRes.append(spanDom);
        };
        renderRes = document.createDocumentFragment();
        renderResArr.forEach((item, i) => {
            // 遍历筛选出来的标签,去源数据里面组装数据。
            // 根据源数据中的表情出现的位置顺序,
            // 去进行组装文字+标签+文字的数据
            // 根据每个出现的表情的前面是否有文字,
            // 标签的后面是否有文字进行拼装数据
            let { name, index, length } = item;
            name = name.split("[")[1].split("]")[0];
            let spanDom = document.createElement('span');
            spanDom.classList.add('chat-emoji');
            let renderText = '',
                rendertextSpan = document.createElement('span');

            if (i === 0) { // 第一项===>标签的[出现在第一项
                renderText = value.slice(0, index);
                rendertextSpan.innerText = rendertextSpan;
                renderRes.append(rendertextSpan); // 文字
                renderEmojiFn(name, spanDom, renderRes); // 表情
                if (renderResArr.length === 1) { // 当前筛选出来的表情只有一个数量的时候
                    let nextRendertext = value.slice(index + length),
                        nextRendertextSpan = document.createElement('span');
                    nextRendertextSpan.innerText = nextRendertext;
                    renderRes.append(nextRendertextSpan); // 文字
                }
            } else if (i === renderResArr.length - 1) { // 最后一项
                renderText = value.slice(length + index); // 在表情的后面文字
                let preItem = renderResArr[i - 1],
                    preIndex = preItem.index + preItem.length,
                    renderPreText = value.slice(preIndex, index), // 在表情的前面的文字
                    renderPreTextSpan = document.createElement('span');

                renderPreTextSpan.innerText = renderPreText;
                renderRes.append(renderPreTextSpan);
                renderEmojiFn(name, spanDom, renderRes);
                rendertextSpan.innerText = renderText;
                renderRes.append(rendertextSpan);
            } else {
                let preItem = renderResArr[i - 1],
                    preIndex = preItem.index + preItem.length;
                renderText = value.slice(preIndex, index);
                rendertextSpan.innerText = renderText;
                renderRes.append(rendertextSpan);
                renderEmojiFn(name, spanDom, renderRes);
            }
        })
    } else {
        const renderResSpan = document.createElement('span');
        renderResSpan.innerText = renderRes;
        renderRes = renderResSpan;
    }
    fragment.append(renderRes);
    return fragment;
}

console.log(scrmTextEmojiRenderFn(str));
console.log(scrmTextEmojiRenderFn(str2));

JavaScript中值在各种场景的转换规则

字符串操作环境数字运算环境逻辑运算环境对象操作环境
undefined“undefined”NaNfalseError
null“null”0falseError
非空字符串不转换字符串对应的数字值true
空字符串不转换0falseString
0“0”不转换falseNumber
NaN“NaN”不转换falseNumber
Infinity“Infinity”不转换trueNumber
Number.POSITIVE_INFINITY“Infinity”不转换trueNumber
Number.NEGATIVE_INFINITY“-Infinity”不转换trueNumber
Number.MAX_VALUE“1.7976931348623157e+308”不转换trueNumber
Number.MIN_VALUE“5e-324”不转换trueNumber
其他所有数字“数字的字符串值”不转换trueNumber
true“true”1不转换Boolean
false“false”0不转换Boolean
对象toString()value()或toString()或NaNtrue不转换

程序员(web前端开发工程师)、手机号码、二进制、十进制、构造函数、substr、parseInt、prototype、length

function PhoneNumber() {
    this.arrayNumber = ['110', '10', '111', '11', '0', '1000', '100', '1', '101', '1001'];
    this.i = 0;
    this.len = 0;
    this.result = '';
}
PhoneNumber.prototype.calculation = function (params = '') {
    this.len = params.length;

    if (!this.len) return '长度不能为空!';
    if (!/^\d+$/.test(Number(params))) return '请输入纯数字!';

    for (; this.i < this.len;)(this.result += parseInt(this.arrayNumber[params[this.i]], 2), this.i++);

    this.result = `${this.result.substr(0, 3)} ${this.result.substr(3, 4)} ${this.result.substr(7)}`;

    return this.result;
}

let phoneNumber = new PhoneNumber();
console.log(phoneNumber.calculation('78159051872'));

JavaScript实现构造函数的封装

function Car(brand, color, displacement) {
    this.brand = brand;
    this.color = color;
    this.displacement = displacement;
    this.info = function() {
        return `颜色为${this.color}${this.brand},排量为${this.displacement}T。`;
    }
}

function Person({
    brand,
    color,
    displacement,
    name,
}) {
    Car.apply(this, [brand, color, displacement]);
    this.name = name;
    this.say = function() {
        console.log(`${this.name}买了一辆${this.info()}`);
        // 半晨买了一辆颜色为寒山暮紫的五菱星辰,排量为1.5T。
    }
}

let infomation = {
        brand: '五菱星辰',
        color: '寒山暮紫',
        displacement: '1.5',
        name: '半晨',
    },
    person = new Person(infomation);

person.say();

JavaScript之粗浅原型和原型链

1、示例代码

function Student(params) {
	this.params = params;
}

let student = new Student('159357');

console.log(student.__proto__ === Student.prototype);
// true
console.log(student.constructor === Student);
// true
console.log(Student.prototype.constructor === Student);
// true

2、注解

1、实例的__proto__全等于函数的原型对象。
2、实例的构造函数全等于函数本身。
3、函数的原型对象的构造函数全等于函数本身。


visualStudioCode自动添加、补全双引号、vsc、配置

1、安装插件

插件简称:Prettier
插件全称:Prettier - Code formatter


2、配置setting.json文件

"prettier.useEditorConfig": false,  
// 是否带分号
"prettier.semi": true,  
// 是否为单引号
"prettier.singleQuote": true  

3、vscode格式化快捷键

Alt + Shift + F


4、相关链接

博客园-vscode 自动添加分号, 双引号


纵向遍历

let sumArray = [];

for (let i = 0; i < series[0].data.length; i++) {
  let sum = 0;
  
  for (let j = 0; j < series.length; j++) {
    sum += series[j].data[i];
  }
  
  sumArray.push(sum);
}

console.log(sumArray);

待定

引用\[3\]中提供了一种方法来输出字符串数组。无论是二维字符数组还是一维字符指针数组,都可以通过循环遍历输出字符串数组中的所有字符串。例如,对于一个二维字符数组,可以使用以下代码来输出字符串数组中的所有字符串: ```c #include <stdio.h> int main() { char strs\[4\]\[10\] = {"tom", "jack", "rose", "lily"}; for (int i = 0; i < 4; i++) { printf("%s\t", strs\[i\]); } printf("\n"); return 0; } ``` 这段代码中,我们使用了一个循环来遍历字符串数组中的每个字符串,并使用`printf`函数来输出每个字符串。每个字符串都使用`%s`格式符进行输出。在循环结束后,我们使用`printf`函数输出一个换行符,以便在输出结果中换行。 请注意,对于一维字符指针数组,也可以使用类似的方法来输出字符串数组中的字符串。只需将循环中的`printf`函数替换为`puts`函数即可。 #### 引用[.reference_title] - *1* [字符数组的输入和输出](https://blog.csdn.net/NuYoaH502329/article/details/127797561)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [C语言零碎知识点字符串数组](https://blog.csdn.net/cnds123321/article/details/122973636)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值