变量和计算
变量类型:值类型 vs 引用类型
值类型
- 存储在栈中;
- 常见的值类型:
number、string、boolean、symbol、undefined
typeof
可判断出值类型
引用类型
- 内存地址存储在栈,值存储在堆;内存地址指向堆;
- 常见的引用类型:
object、array
; - 特殊的引用类型:
null
(null 的值是空,并不是一个内存地址) - 特殊引用类型:
fuction
(函数是一个功能,不用于存储数据,所以没有“拷贝、复制函数”这一说法)
堆栈模型
出处:https://coding.imooc.com/learn/list/400.html
栈是从上往下累加,堆是从下往上累加。
复杂数据类型的值存储在堆内存,地址(指向堆中值)存储在栈内存,当我们把对象赋值给另外一个变量的时候,赋值的是地址,指向同一块内存,当一个对象改变时,另外一个也会变化。
typeof 可以判断哪些类型
typeof
可以判断出所有值类型:;typeof
可以判断函数;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'
手写深拷贝
- 注意判断值类型和引用类型;
- 注意判断是数组还是对象;
- 递归。
/**
深拷贝
@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
js
函数中参数的传递是值传递还是引用传递?
答:都是值传递。引用类型变量的话,就是把内存地址当做值传递给参数,所以引用类型也算是值传递。- 如果是正则或者日期怎么判断数据类型?
答:instanceof Date;instatnceof RegExp;
null
属于特殊的引用类型的依据是什么?
答:null
的值是空,并不是一个内存地址。- 为什么引用类型的值不存在栈中?
答:因为值类型占的内存比较小,引用类型占的内存比较大,如果放在栈中,复制起来也会很慢。 var
、let
、const
的区别。
- var 是
ES5
语法,let
、const
是ES6
语法;var
有变量提升; var
和let
是变量,可修改;const
是常量,不可修改;let
、const
有块级作用域;var
没有。
console.log(a); // undefined
var a = 100;
等同于
var a;
console.log(a); // undefined
a = 100;
- 函数声明式和函数表达式的区别。
// 函数声明式
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;
};
typeof
可以判断出哪些类型
- 值类型:
number、string、boolean、undefined、symbol
- 引用类型,但不能细分;
function
- 列举强制类型转换和隐式类型转换。
- 强制类型转换:
parseInt、parseFloat、toString
等。 - 隐式类型转换:
if
语句;逻辑运算;==
;+
拼接字符串。
- 数组的 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
slice
和splice
的区别。
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]
[10, 20, 30].map(parseInt)
的结果。
[10, 20, 30].map((item, index) => {
return parseInt(item, index);
});
// [10, NaN, NaN]
// 第二个参数表示的是进制,2-36 的范围,不填默认 10 进制,0 也是 10 进制
- 数组如何去重。
- 传统放肆好:遍历元素挨个比较、去重
- 使用 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];
}