JS 基础知识 - 变量类型和计算

变量和计算

变量类型:值类型 vs 引用类型

值类型

  1. 存储在中;
  2. 常见的值类型:number、string、boolean、symbol、undefined
  3. typeof 可判断出值类型

引用类型

  1. 内存地址存储在存储在;内存地址指向堆;
  2. 常见的引用类型:object、array
  3. 特殊的引用类型:null(null 的值是空,并不是一个内存地址)
  4. 特殊引用类型:fuction(函数是一个功能,不用于存储数据,所以没有“拷贝、复制函数”这一说法)

堆栈模型

出处:https://coding.imooc.com/learn/list/400.html
出处:https://coding.imooc.com/lesson/400.html

栈是从上往下累加,堆是从下往上累加。
复杂数据类型的值存储在堆内存,地址(指向堆中值)存储在栈内存,当我们把对象赋值给另外一个变量的时候,赋值的是地址,指向同一块内存,当一个对象改变时,另外一个也会变化。

typeof 可以判断哪些类型

  1. typeof 可以判断出所有值类型:;
  2. typeof 可以判断函数;
  3. typeof 可以识别出引用类型(不能再继续识别)。
// 判断所有值类型
let a; // typeof a === 'undefined'
const str = 'abc'; // typeof str === 'string'
const n = 100; // typeof n === 'number'
const b = true; // typeof b === 'boolean'
const s = Symbol('s'); // typeof s === 'symbol

// 能判断函数
typeof console.log // 'function'
typeof function () {} // 'function'

// 能识别引用类型(不能再继续识别)
typeof null // 'object'
typeof ['a', 'b'] // 'object'
typeof { x: 100 } // 'object'

手写深拷贝

  1. 注意判断值类型和引用类型;
  2. 注意判断是数组还是对象;
  3. 递归。
/**
深拷贝
@params {Object} obj 要拷贝的对象
*/
fucntion deepClone(obj = {}) {
	if (typeof obj !== 'object' || obj == null) {
		// obj 是 null、不是对象或数组,直接返回
		return obj;
	}
	
	// 初始化返回结果
	let result;

	if (obj instanceof Array) {
		result = [];
	} else {
		result = {};
	}

	for (const key in obj) {
		// 保证 key 不是原型的属性
		if (obj.hasOwnerProperty(key)) {
			// 递归调用
			result[key] = deepClone(obj[key]);
		}
	}

	// 返回结果
	return result;
}

简化版:

function deepClone(obj = {}) {
    // 1 判断是否是非应用类型或者null
    if (typeof obj !== 'object' || obj == null) return obj
    // 2 创建一个容器
    let cloneObj = new obj.constructor()
    // 3 拿到对象的keys,给容器赋值
    Object.keys(obj).forEach(v => cloneObj[v] = deepClone(obj[v]))
    // 4 返回容器
    return cloneObj
}

变量计算-类型转换

字符串拼接

const a = 100 + 10; // 110
const b = 100 + '10'; // '10010'
const c = true + '10'; // 'true10'

== 运算符

// == 运算符会隐式转换
100 ==100// true
0 == '' // true
0 == 'false // true
false == '' // true
null == undefined // true

// 除了 == null 之外,其他一律用 ===,例如:
const obj = { x: 100 };
if (obj.a == null) { }
// 相当于
// if (obj.a === null || obj.a === undefined) { }

if 语句和逻辑运算

truely 变量:!!a === true 的变量
falsely 变量:!!a === false 的变量

// 一下是 falsely 变量。除此之外都是 truely 变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false

if 语句判断的都是 truely 变量和 falsely 变量。

逻辑判断

console.log(10 && 0); // 0
console.log('' || 'abc'); // 'abc'
console.log(!window.abc) // true

Map 和 Set

有序和无序

  • Object 无序;Array 有序。
  • 有序操作慢;无序操作快,但无序。

Map 和 Object 的区别

  • API 不同,Map 可以以任意类型为 key
  • Map 是有序结构
  • Map 操作同样很快
// const obj = {
//     key1: 'hello',
//     key2: 100,
//     key3: { x:100 }
// }

const m = new Map([
	['key1', 'hello'],
	['key2', 100],
	['key3',  { x: 100 }]
]);
m.set('name', '章三'); // 新增
m.set('key1', 'hello world'); // 修改
m.get('key1'); // 获取
m.delete('key2'); // 删除 返回 true/false
m.has('key3'); // 是否存在 key3  返回 true/false
m.forEach((value, key) => console.log(key, value));
m.size // 获取长度

// Map 以任意类型为 key
const o = { name: 'xxx' };
m.set(o, 'object key');
function fn() {};
m.set(fn, 'fn key');

// obj1 , obj2
// obj1.xxx = obj2
// m.set(obj1, obj2) // 关联,但是没有引用关系,保持独立
// m.get(obj1) // obj2

// object 无序
// const obj = {  2: 20, 3: 30, 1: 10 }
// console.log(Object.keys(obj))

// Map 有序的,但是还很快
const m1 = new Map([
	['key1', 'hello'],
	['key3', 100],
	['key2',  { x: 100 }]
]);
m.forEach((value, key) => console.log(key, value))

// object 有多块?
const obj = {};
for (let i = 0; i < 1000 * 10000; i++) {
	obj[i + ''] = i;
}

console.time('obj find');
obj['5000000'];
console.timeEnd('obj find');
console.time('obj delete');
delete obj['5000000'];
console.timeEnd('obj delete');

// Map 有多块
const m = new Map()
for (let i = 0; i < 1000 * 10000; i++) {
	m.set(i + '', i)
}

console.time('map find')
m.has('5000000')
console.timeEnd('map find')
console.time('map delete')
m.delete('5000000')
console.timeEnd('map delete')

Set 和 Array 的区别

  • API 不同
  • Set 元素不能重复
  • Set 是无序结构,操作很快
// const arr = [1, 10, 20, 30, 40, 50];

const set = new Set([10, 20, 30, 40]);

set.add(50); // 新增
set.delete(20); // 删除
set.has(30); // 是否存在 返回 true/false
set.size; // 长度
set.forEach(val => console.log(val)); // 没有 index

// Set 元素是不能重复的(数组去重)
// Set 是无序的(快), arr 是有序的(慢)

// arr 有多慢?
const arr = [];
for (let i  = 0; i < 100 * 10000; i++) {
	arr.push(i);
}

console.time('arr unshift');
arr.unshift('a');
console.timeEnd('arr unshift');

console.time('arr push');
arr.push('b');
console.timeEnd('arr push');

console.time('arr find');
arr.includes(500000);
console.timeEnd('arr find');

// set 有多快?
const set = new Set();
for (let i  = 0; i < 100 * 10000; i++) {
	set.add(i);
}

console.time('set add');
set.add('a');
console.timeEnd('set add');

console.time('set find');
set.has(500000);
console.timeEnd('set find');

WeakMap 和 WeakSet

  • 弱引用,防止内存泄漏
  • WeakMap 只能用对象作为key;WeakSet 智能用对象作为 value
  • 没有 forEach 和 size,只能用 add、delete、has
// WeakMap 弱引用,防止内存泄漏
const wMap = new WeakMap();

function fn() {
	const obj = { name: '章三' };
	wMap.set(obj, 'name info'); // 只能用对象作为 key
}

fn();
// 没有 forEach size ,只能 has delete add
// gc 垃圾清理不一定是及时的

// WeakMap 场景
const userInfo = { name: '章三' };
const cityInfo = { city: '北京' };
userInfo.city = cityInfo;
wMap.set(userInfo, cityInfo); // 建立一种关联关系,而且两者保持独立,而且**不影响彼此的销毁逻辑**
wMap.get(userInfo);

// WeakSet 弱引用,防止内存泄漏,只能用对象作为 value 
// 没有 forEach size ,只能 has delete add
const wSet = new WeakSet();

function fn() {
	const obj = { name: '章三' }
    wSet.add(obj);
}

fn();

reduce

arr.reduce(fn, initParams):第一个参数是函数,第二个参数是初始化的值。
fn(sum, currentValue, index, arr):第一个参数是每次 return 的值(初始化时是 initParams),第二个参数是每次遍历的值,第三个参数是每次遍历的 index,第四个参数是操作的对象。

// 数组求和 - 传统模式
function sum(arr) {
	let res = 0; // 需要新定义一个变量
	arr.forEach(n => res = res + n);
	return res;
}

// 数组求和 - reduce 方式
const arr = [10, 20, 30, 40, 50];
const res = arr.reduce((sum, curVal, index, arr) => {
	// console.log('reduce function ....');
	// console.log('sum', sum);
	// console.log('curVal', curVal);
	// console.log('index', index);
	// console.log('arr', arr);
	
	return sum + curVal; // 返回值,会作为下一次执行时的第一个参数 sum 的值
}, 0);

const res = arr.reduce((sum, curVal) => sum + curVal, 0);
console.log('res', res);


// 计数 - reduce 方式
const arr = [10, 20, 30, 40, 50, 10, 20, 30, 20];
const n = 30;
const count = arr.reduce((count, val) => {
	return val === n ? count + 1 : count;
}, 0);
console.log('count', count);

// 输出字符串
const arr = [
	{ name: '张三', age: '20' },
	{ name: '李四', age: '21' },
	{ name: '小明', age: '22' }
];

// 传统方式
const str = arr.map(item => {
	return `${item.name} - ${item.age}`;
}).join('\n');
console.log(str);

// reduce 方式
const str = arr.reduce((s, item) => {
	return `${s}${item.name} - ${item.age}\n`;
}, '');
console.log(str);

FQA

  1. js 函数中参数的传递是值传递还是引用传递?
    答:都是值传递。引用类型变量的话,就是把内存地址当做值传递给参数,所以引用类型也算是值传递。
  2. 如果是正则或者日期怎么判断数据类型?
    答:instanceof Date;instatnceof RegExp;
  3. null 属于特殊的引用类型的依据是什么?
    答:null 的值是空,并不是一个内存地址。
  4. 为什么引用类型的值不存在栈中?
    答:因为值类型占的内存比较小,引用类型占的内存比较大,如果放在栈中,复制起来也会很慢。
  5. varletconst 的区别。
  • var 是 ES5 语法,letconstES6语法;var有变量提升;
  • varlet是变量,可修改;const是常量,不可修改;
  • letconst有块级作用域;var没有。
console.log(a); // undefined

var a = 100;

等同于

var a;

console.log(a); // undefined

a = 100;
  1. 函数声明式和函数表达式的区别。
// 函数声明式
console.log(sum(10, 20)); // 30

// 函数声明式的函数会预先加载
function sum(a, b) {
	return a + b;
}


console.log(sum1(10, 20)); // 会报错

// 函数表达式
const sum1 = function (a, b) {
	return a + b;
};
  1. typeof 可以判断出哪些类型
  • 值类型:number、string、boolean、undefined、symbol
  • 引用类型,但不能细分;
  • function
  1. 列举强制类型转换和隐式类型转换。
  • 强制类型转换:parseInt、parseFloat、toString 等。
  • 隐式类型转换:if 语句;逻辑运算;==+ 拼接字符串。
  1. 数组的 pop、push、unshift、shift 分别是什么。
const arr1 = [10, 20, 30, 40];

const num1 = arr1.pop(); // 返回 40
// 原数组 arr1: [10, 20, 30]

const arr2 = [10, 20, 30, 40];

const num2 = arr2.push(50); // 返回 length 5
// 原数组 arr2: [10, 20, 30, 40, 50]

const arr3 = [10, 20, 30, 40];

const num3 = arr3.unshift(0); // 返回 length 5
// 原数组 arr3: [0, 10, 20, 30, 40]

const arr4 = [10, 20, 30, 40];

const num4 = arr1.shift(); // 返回 10
// 原数组 arr1: [20, 30, 40]


// 纯函数:1.不改变原数组(没有副作用) 2. 返回一个数组
// concat、map、filter、slice
const arr = [10, 20, 30, 40];

const a1 = arr.concat([50, 60, 70]);
const a2 = arr.map(num => num * 10);
const a3 = arr.filter(num => num > 25);
const a4 = arr.slice();

// 非纯函数
// pop、push、unshift、shift
// forEach
// some、every
// reduce
  1. slicesplice 的区别。
const arr = [10, 20, 30, 40, 50];

const arr1 = arr.slice(); // 拷贝数组 [10, 20, 30, 40, 50]
// 第一个参数是截取的开始位置下标 第二个参数是截取该下标之前的参数(不包含该下标)
const arr2 = arr.slice(1, 4); // [20, 30, 40]
// 截取这个下标开始的数据直到最后一个
const arr3 = arr.slice(2); // [30, 40, 50]
// 截取最后两个
const arr4 = arr.slice(-2); // [40, 50]
const arr = [10, 20, 30, 40, 50];

// 第一个参数是截取的开始位置下标 第二个参数是要截取的个数 后面的所有参数是要替换的内容
const arr1 = arr.splice(1, 2, 'a', 'b', 'c'); // 截取数组并替换片段 
// arr1: [20, 30]
// arr: [10, 'a', 'b', 'c', 40, 50]

const arr3 = [10, 20, 30, 40, 50];

const arr4 = arr3.splce(1, 3); // [20, 30, 40]
// arr3: [10, 50]]

const arr5 = [10, 20, 30, 40, 50];
// 第二个参数如果是 0 就表示在第一个参数的下标位置插入片段
const arr6 = arr5.splice(2, 0, 'a'); // []
// arr: [10, 20, 'a', 30, 40, 50]
  1. [10, 20, 30].map(parseInt) 的结果。
[10, 20, 30].map((item, index) => {
	return parseInt(item, index);
});
// [10, NaN, NaN]

// 第二个参数表示的是进制,2-36 的范围,不填默认 10 进制,0 也是 10 进制
  1. 数组如何去重。
  • 传统放肆好:遍历元素挨个比较、去重
  • 使用 Set
function unique(arr) {
	const res = [];

	arr.forEach(item => {
		if (res.indexOf(item) < 0) {
			res.push(item);
		}
	});

	return res;
}

// 使用 Set
function unique(arr) {
	const set = new Set(arr);

	return [...set];
}
  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值