javascript
数据类型
数据类型
- 基本类型: number / string / boolean / undefined / null / symbol
- 引用类型: object / array / function
数据类型检测
typeof
- string number boolean object undefined function symbol
- 区分不了null object array
instanceof
- instanceof 是通过原型链来判断数据类型的。
- 如: [] instanceof Array
- 再如: let str = new String('test'); str instanceof String
Object.prototype.toString.call()
- Object原型链上的toString方法。
- 返回[object type]
- [object Number / String / Object / Array / Undefined / Null / Boolean]
深拷贝和浅拷贝
浅拷贝和深拷贝只是针对引用类型的,如Object/ array /function。
浅拷贝只是复制了一层对象属性,而深拷贝则复制了对象的所有属性层级。改变初始值的引用类型值,浅拷贝的值会随着更改,深拷贝到值也不会被改变。
浅拷贝
- Object.assign(obj)
深拷贝
- JSON.parse(JSON.stringfy(obj)) => 对象上的函数属性不会被拷贝到新对象上。
- { ...obj } => 可以拷贝所有的基本类型和引用类型。
js实现一个深拷贝
function clone(source){
// 如果是源对象是null, 则返回null
if( !typeof source === 'object'|| source === null) return source;
// 非引用类型的源数据,如字符串和数字、布尔值,直接返回。
if( typeof source !== 'object' ) return source;
// 判断源数据是对象还是数组,然后区分赋值。 Array.isArray
let target = Array.isArray(source) ? [] : {};
// 获取对象所有的属性名或者数组的下标
for (const key in source) {
// 判断是不是私有属性
if (source.hasOwnProperty(key)) {
const el = source[key];
// 如果值是引用类型,则继续重新遍历
if( typeof el === 'object'){
target[key] = clone(source[key]);
}else{
// 否则,则赋值到新对象中
target[key] = source[key];
}
}
};
return target;
}
作用域
变量声明提升
- var 声明的变量和function会被提升到js代码的顶部。
- 只是提升变量,赋值并不会提升,在其声明之前打印的话,会打印出undefined。
- const 和 let 声明的变量和常量,则没有变量声明提升的问题,在声明之前使用,会报错。
- 函数声明的优先级高于变量声明,如果存在同名的函数和变量,在变量未赋值的情况下,函数会覆盖变量。
作用域链
- 因为函数的嵌套,而出现了作用域的层级关系,在函数执行时候,所使用到的变量如果在当前函数作用域内没有找到,则会向其上一级函数的作用域内寻找,如果还没有,则继续向上,直到找到全局作用域window为止,这就是作用域链。
闭包
- 闭包就是将函数内部和函数外部链接起来,可以在函数外部使用函数内部的变量。
- 同时让这些变量一直保存在内存中。
- 函数内部抛出一个函数,抛出的函数使用着函数内部的变量。在外部接收抛出的函数,函数内部声明的变量不会被释放,会一直存在内存中。
function outer(){
let num = 100;
return function inner(){
console.log( num ++ );
}
};
let func = outer();
func();
func();
原型和继承
js创建对象的几种方式
- let obj = {};
- let obj = Object.create({a: 1});
- let obj = new Object();
js如何实现一个类
-
构造函数
// 会使用到prototype,书写比较麻烦 function Person(name, age){ this.name = name; this.age = age; this.showName = () => { console.log( this.name ); } }; Person.prototype.showInfo = function(){ return this.name + ' --- ' + this.age }
-
Class(es6语法糖)
class Person{ constructor(name, age){ this.name = name; this.age = age; this.showName = () => { console.log( name ); } }; showInfo(){ return this.name + ' --- ' + this.age } }
原型链
- 当我们使用对象上的一个属性的时候,会现在对象自身寻找这个属性,如果找不到,则会沿着对象的隐式原型
__proto__
逐层向上寻找,直到找到null为止。这条由__proto__
形成的链子,就是原型链。 prototype
__proto__
constructor
new
继承
js如何实现继承
Object.create(obj, {})
{}.__proto__ = prototype
new Constructor | class
- Object.setPrototypeOf(obj, prototype)
new和this
new操作符做了什么
- 创建一个实例对象{}
- 将this指向该实例对象,同时该实例对象继承了构造函数的原型
- 然后属性和方法都添加到了this引用的对象中。
- 新创建的对象由this所引用,最后隐式的返回this.
new的模拟实现
this对象的理解
- this总是指向函数的直接调用者
- 在普通函数中,this指向window。
- 在箭头函数中,this指向父级作用域。
- 对于构造函数,this指向调用的实例。
call、apply和bind
- 都是改变函数的this指向问题。
- call(this, p1, p2, ... ),会立刻执行函数,可以传递多个参数。
- bind(this, p1, p2, ... ), 不会立刻执行函数,可以传递多个参数。
- apply(this, [p1, p2, ...]),传递的参数是一个数组格式
数据处理
数据去重
let arr = [1,2,3,4,5,6,7,2,3,4,7,8,9,7];
// es6 Set & Array.from()
let set = new Set(arr);
Array.from(set);
// map & includes & push
function counterSingle (list) {
let _list = [];
list.map( i => !_list.includes(i) && _list.push( i ) );
return _list;
}
counterSingle(arr);
数据排序
let arr = [1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 7, 8, 9, 7];
// api
arr.sort( (a, b) => a -b );
// 重写
function arraySort(arr){
for( var i = 0; i < arr.length; i ++ ){
for( var j = i + 1; j < arr.length; j ++ ){
if( arr[i] > arr[j] ){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
};
return arr;
};
arraySort(arr);
递归求和
let arr = [1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 7, 8, 9, 7];
let number = 100;
let num = arr.reduce((acc, cur) => acc + cur );
console.log( num );
let totalNumber = 0;
for( var i = 0; i <= number; i++ ){
totalNumber += i;
};
console.log( totalNumber );
递归求积
function multiple (number) {
return number * (number === 1 ? 1 : multiple( number - 1 ));
}
console.log( multiple(5));
计算数组各项的重复次数
let arr = [1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 7, 8, 9, 7];
// 计算重复次数
let obj = {};
for (const value of arr) {
obj[value] ? obj[value] ++ : obj[value] = 1;
};
console.log( obj );
斐波那契数列
function fbnq(len){
let list = [ 1, 1 ];
for( var i = 1; i < len - 1; i ++){
list.push( list[i] + list[ i - 1 ] );
};
return list;
};
fbnq(10);
数组最大差值
// Math
maxDiffer = () => {
let list = [20, 40, 123, 123, 34, 45, 454, 30];
let max = Math.max( ...list );
let min = Math.min( ...list );
return max - min;
}
// 手写
maxDiffer = () => {
let list = [20, 40, 123, 123, 34, 45, 454, 30];
let max = list[0];
let min = list[0];
for( var i = 1; i < list.length; i ++ ){
if( list[i] > max ){
max = list[i];
}
if( list[i] < min ){
min = list[i];
}
}
return max - min;
}
console.log( maxDiffer() )
Event Loop
堆、栈、队列
- 堆: 数据结构,是利用完全二叉树维护的一组数据。
- 栈:是一种线性表,遵循着
先进后出
的原则,从尾部插入和执行。 - 队列:遵循着
先进先出
的原则,从表的尾部插入,头部删除。 - js执行栈: js是单线程语言,有一个主线程(main thread)和执行栈(call-stack),所有的主线程任务都会被放到执行站中等待主线程执行。
宏任务和微任务
- 宏任务(macro-task):当前调用栈中执行的全部任务被称为宏任务,包括script全部的代码、settTimeout、setInterval等。
- 微任务(micro-task):当前(此次事件循环中)宏任务执行完成后,下一次要被执行的任务称为微任务,包括promise、async/await等。
- 不同类型的任务会进入相对于的事件队列(Event Queue),宏任务会被放到回调队列(消息队列 callback queue)中,由事件触发线程维护;微任务会被放到微任务队列中,由js引擎线程维护。
一句话解析什么是event loop
- js是单线程,其多线程和异步就是靠event loop(事件轮询)来实现的。
- js从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中。
- 当程序调用外部的API(如ajax, setTimeOut)时,会将该异步任务挂起,放到消息队列中,继续执行执行栈中的任务。遇到promise、async/await的时候,会放到微任务队列中。
- 主线程执行完之后,会先看微任务队列中是否有任务,有的话会立即执行,在执行过程中遇到的微任务,也会同步执行。
- 执行完微任务队列,或者微任务队列未空时,则会执行消息任务中的任务。如在执行的时候遇到异步任务,则会继续放到消息队列中。
- 方法和程序在执行完之后,该方法和程序会从执行站中被弹出。
- 主线程每次将执行站清空之后,都会去事件队列中查看是都有任务需要执行,如果有,就取出一个放到执行栈中执行,这个循环的过程就是Event Loop(事件循环)。
浏览器页面渲染过程
- 浏览器解析html代码,创建一个DOM树。并行请求css、js、img。
- 浏览器解析css代码,计算出最终的样式数据,构建出cssDOM树(rendering tree)。
- css优先级: html中的css > 内联样式 > 外部样式 > 用户设置 > 浏览器默认样式
- 等到渲染树创建好之后,浏览器会根据渲染树的结果将页面绘制在屏幕上。
- css和js往往会多次修改dom和cssdom。
- dom树和渲染树的区别: 渲染树有样式,例如display:none的节点就会从渲染树上移除。
浏览器缓存
浏览器缓存的认识
- 强缓存
- 主要是利用Expires 和 Cache-Control这两个字段,在有效期内,会直接读取本地的缓存文件,而不会向浏览器发送请求。
- Expires 是一个时间点,依赖的是客户端的时间,在到达这个时间店之后,缓存资源会过期,要向服务器发送请求。
- Cache-Control 是一个相对时间,可以设置max-age,在多少秒之后过期。其优先级也比Expires更高。
- 协商缓存
- 由服务器来确定缓存资源是否可用,需要服务器和客户端一起配合使用。
- 服务器会在response header中返回last-modified 以及 ETag 字段。
- last-modified 表示资源在服务器中最后一次的修改时间,再次请求该资源的时候,请求头会带上 if-Modified-Since,询问该资源是否有被更新。
- 每次文件修改后服务端那边会生成一个新的 ETag ,当再次请求该资源时候,浏览器的request header中会带上If-None-Match ,这值就是之前返回的ETag ,询问该资源 ETag 是否变动,有变动的话,则需要更新资源。
常用的前端缓存
-
cookie
- 存储在客户端,有大小限制,4kb左右。
- 可以设置过期事件,默认未浏览器关闭的时间。
-
Session
- 存储在服务器,较为安全。
-
SessionStorage
- 客户端本地存储,浏览器关闭后就会被清除。大小为5MB
-
LocalStorage
- 客户端本地存储,不手动清除的话,会一直保存在浏览器中。
css
盒模型
- 盒模型由内到外分别是: content 、 padding、 border、 margin
- 在ie中, width = content + padding + border
- 在标准的盒子模型中: width = content
- box-sizing : border-box | content-box(默认)
居中
水平居中
- 行内元素: text-align: center;
- 块状元素: margin:0 auto;
- position: relative; left: 50%; transform: translateX( -50% )
- display: flex; justify-content: center
垂直居中
- position: relative; top: 50%; transform: translateY(-50%);
- display: flex; align-items: center;
- height: 100px; line-height: 100px;