共计 9547 个字符,预计需要花费 24 分钟才能阅读完成。
span 的 display 值,文本 example 的颜色
<div class="outside">
<span id="passage" style="color:blue;" data-color="red">example</span>
</div>
<style>
#passage {color: yellow;}
.outside span{color: green; display: block;}
span {display: inline;}
[data-color="red"] {color: red;}
</style>
其实浏览器中,这张图的排列顺序,就很好的表示出了这个 demo 中的优先级关系:
优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。⚠️
!important
是个例外,优先级最高。
更详细的 CSS 优先级请查看 MDN- 优先级是如何计算的?
写一个满屏的品字
这就是考验一个布局的能力,没什么好说的,办法很多。我用的 flex 打个样。
<div class="main">
<div class="top"><h1>class="top"</h1></div>
<div class="bottom">
<div class="left">
<h1>class="left"</h1>
</div>
<div class="right">
<h1>class="right"</h1>
</div>
</div>
</div>
<style>
.main{
display: flex;
flex-direction: column;
}
.top,.bottom{height: 300px;}
.top{border: 1px solid red;}
.bottom{
display: flex;
border: 1px solid green;
}
.left,.right{
flex: 1;
height: 100%;
}
.left{border-right: 1px solid blue;}
</style>
如下代码,写出执行结果
var fun = function(arr) {for(var i = 0; i< arr.length;i++) {setTimeout(function() {console.log(i);
},0)
}
console.log(arr[i])
}
fun([1,2,3,4])
直接写答案就没什么意思了,借这个题先扯一下 执行上下文
、 作用域
、 作用域链
、 闭包
。
执行上下文
以下 demo、图示、结论绝大部分来自这个网站,推荐阅读!在这里引用是为了让大家更好的理解,我确实讲不了这么好!!!
一段 JavaScript 的代码执行的时候,都会产生一个执行上下文(也就是执行环境)。多段代码执行就会产生多个执行上下文。
console.log(1);
// 这段代码的执行上下文就是 -- 全局环境
function test() {console.log('test');
}
test();
// test() 执行上下文就是 test-- 函数环境
JavaScript 中的运行环境大概包括三种情况:
- 全局环境:JavaScript 代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
⚠️JavaScript 引擎会以栈的形式来处理这些执行上下文,栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。
看下面这个 demo,相信大家一看就懂了:
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();}
changeColor();
这里面有 全局上下文 (Global Context)
、changeColor() 上下文
、swapColors() 上下文
,它们进栈出栈如下图:
每一个执行上下文都有自己的生命周期:
对执行上下文总结一些结论:
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈
- 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
作用域、作用域链与闭包
作用域与执行上下文是完全不同的两个概念。
JavaScript 代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
⚠️JavaScript 中只有全局作用域与函数作用域(因为 eval 我们平时开发中几乎不会用到它,这里不讨论)。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
看一个 demo:
var a = 20;
function test() {
var b = a + 10;
function innerTest() {
var c = 10;
return b + c;
}
return innerTest();}
test();
在上面的例子中,全局,函数 test,函数 innerTest 的执行上下文先后创建。我们设定他们的变量对象分别为 VO(global),VO(test), VO(innerTest)。而 innerTest 的作用域链,则同时包含了这三个变量对象,所以 innerTest 的执行上下文可如下表示。
innerTestEC = {VO: {...}, // 变量对象
scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
}
至于这里面的 VO AO 有兴趣的可以去上面那个网站里看看,这里不提,我觉得不妨碍大家理解。
简单说就是,在 innerTest 这个方法内,能拿到 test()方法中的变量,也能拿到全局环境中的变量,这就形成了一个作用域链。
看到这里相信大家都知道了,闭包不就是这个东东嘛。
它由两部分组成。执行上下文 (代号 A),以及在该执行上下文中创建的函数(代号 B)。
当 B 执行时,如果访问了 A 中变量对象中的值,那么闭包就会产生。
JavaScript 拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。
而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。
setTimeout
绕了一圈回到这个题,这个题中的 setTimeout 又在何时执行呢?
在这里,将会介绍另外一个特殊的队列结构,页面中所有由 setTimeout 定义的操作,都将放在同一个队列中依次执行。
而这个队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由 setTimeout 定义的操作。而这些操作进入队列的顺序,则由设定的延迟时间来决定。
- 这个题中循环 4 次,每次往队列里加入一个
console.log(i)
, 它们引用的都是同一个i
在循环结束时,i
已经变成 4 了。 -
console.log(arr[i])
就是undefined
。
答案:
undefined
,4 ,4 ,4 ,4
如下代码,写出执行结果
function person(name) {if(name) {this.name = name;}
console.log(this.name);
}
person.prototype.name = 'Tom';
var human = {
person: person,
name: 'Cat'
}
person();
person('Jack');
new person();
new person('Rose');
human.person();
person.call(window)
-
person()
, 作为函数直接调用,this
指向window
,this.name = window.name=undefined
-
person('Jack')
, 跟上面一样,this
指向window
,this.name = window.name=name='Jack'
-
new person()
, 作为构造函数调用,this
指向新生成的对象,在自身没有找到this.name
就会沿着原型链查找,所以this.name = person.prototype.name=Tom
-
new person('Rose')
, 与上面类似,区别在于传了name
-
human.person()
, 作为对象方法调用,this
指向human
=>human.name = 'Cat'
-
person.call(window)
, 用 call 方法将this
指向window
,⚠️这里最容易错❌,person('Jack')
已经将window.name='Jack'
答案:undefined、Jack、Tom、Rose、Cat、Jack
如下代码,写出执行结果
var a = window.a = 'finget.github.io'
function hello(){console.log(a);
var a = 'hello';
console.log(a);
console.log(b);
let b = 'finget';
}
hello();
这个题比较简单,主要涉及的就是变量提升,和作用域链。坑点就是第一个 console.log(a)
到底是打印 undefined
还是finget.github.io
。
再看看作用域链那张图:
在 hello()
方法中定义了一个 var a = 'hello'
, 虽然在刚执行的时候,根据变量提升原则,a=undefined
,但是它还是很有骨气的,只要自己有绝不往上找。那如果换成let a = 'hello'
呢?
来来来试一试:
var a = window.a = 'finget.github.io'
function hello(){console.log(a);
let a = 'hello';
console.log(a);
console.log(b);
let b = 'finget';
}
hello();
暂时性死区
只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量 tmp
,但是块级作用域内let
又声明了一个局部变量 tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对 tmp
赋值会报错。
上面的结果就很清楚了,直接报错,后面的也不执行。
提取 url 的参数,以 key-value 形式返回
完全考正则,自己恶补吧。没办法!
let getSearch = function(url) {let matched = /^(?:https?:\/\/[^?]*\?)(.*)/gi.exec(url)
return matched ? matched[1] : ''
}
// 递归函数,循环匹配 search
let searchFn = function (search, query) {if (search) {let matched = /(\w+)=(\w*)/g.exec(search)
if (matched) {query[matched[1]] = decodeURIComponent(matched[2])
searchFn(search.slice(matched.index + matched[0].length), query)
}
}
}
let parseUrl = function (url) {let query = {}
searchFn(getSearch(url), query)
return query
}
let url = 'http://localhost:3009/h5/test?recordID=161851&order=2'
console.log(parseUrl(url)) // => {recordID: '161851', order: '2'}
判断一个字符串中出现最多的字符,统计次数
function maxStr(str) {let map = {}
for(let v of str) {map[v] = ~~map[v] + 1
}
// 这里就类似这种结构 map={a:1,b:1}, ~~map[v]就类似 parseInt(), 如果某一个字符第一出现就是 0,=> 0+1,以此类推!// Object.values 能将一个对象的 value 返回成一个数组,再去最大值
let max = Math.max(...Object.values(map))
for (let key in map) {if (map[key] == max){return {[key]: max}
}
}
}
let str = 'aasdfasd,asdfjaslkdfjiqjwioaklsdf,asd,lqwejrio1ji3wioqjroiqqewslkasm'
console.log(maxStr(str))
按位非运算符“~”先看看 w3c 的定义:位运算 NOT 由否定号(~)表示,它是 ECMAScript 中为数不多的与二进制算术有关的运算符之一。位运算 NOT 是三步的处理过程:把运算数转换成 32 位数字
把二进制数转换成它的二进制反码(0->1, 1->0)把二进制数转换成浮点数
简单的理解,对任一数值 x 进行按位非操作的结果为 -(x + 1)
console.log('~null:', ~null); // => -1
console.log('~undefined:', ~undefined); // => -1
console.log('~0:', ~0); // => -1
console.log('~{}:', ~{}); // => -1
console.log('~[]:', ~[]); // => -1
console.log('~(1/0):', ~(1/0)); // => -1
console.log('~false:', ~false); // => -1
console.log('~true:', ~true); // => -2
console.log('~1.2543:', ~1.2543); // => -2
console.log('~4.9:', ~4.9); // => -5
console.log('~(-2.999):', ~(-2.999)); // => 1
那么, ~~x 就为 -(-(x+1) + 1) 相当于是 parseInt()
console.log('~~null:', ~~null); // => 0
console.log('~~undefined:', ~~undefined); // => 0
console.log('~~0:', ~~0); // => 0
console.log('~~{}:', ~~{}); // => 0
console.log('~~[]:', ~~[]); // => 0
console.log('~~(1/0):', ~~(1/0)); // => 0
console.log('~~false:', ~~false); // => 0
console.log('~~true:', ~~true); // => 1
console.log('~~1.2543:', ~~1.2543); // => 1
console.log('~~4.9:', ~~4.9); // => 4
console.log('~~(-2.999):', ~~(-2.999)); // => -2
实现一个拷贝函数
JSON.parse()
const newObj = JSON.parse(JSON.stringify(oldObj));
️1. 他无法实现对函数、RegExp 等特殊对象的克隆
2. 会抛弃对象的 constructor, 所有的构造函数会指向 Object
3. 对象有循环引用, 会报错
比较完善的深拷贝
我觉得面试手写这个也太那啥了!
const isType = (obj, type) => {if (typeof obj !== 'object') return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case 'Array':
flag = typeString === '[object Array]';
break;
case 'Date':
flag = typeString === '[object Date]';
break;
case 'RegExp':
flag = typeString === '[object RegExp]';
break;
default:
flag = false;
}
return flag;
};
const getRegExp = re => {
var flags = '';
if (re.global) flags += 'g';
if (re.ignoreCase) flags += 'i';
if (re.multiline) flags += 'm';
return flags;
};
const clone = parent => {
// 维护两个储存循环引用的数组
const parents = [];
const children = [];
const _clone = parent => {if (parent === null) return null;
if (typeof parent !== 'object') return parent;
let child, proto;
if (isType(parent, 'Array')) {
// 对数组做特殊处理
child = [];} else if (isType(parent, 'RegExp')) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, 'Date')) {
// 对 Date 对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用 Object.create 切断原型链
child = Object.create(proto);
}
// 处理循环引用
const index = parents.indexOf(parent);
if (index != -1) {
// 如果父数组存在本对象, 说明之前已经被引用过, 直接返回此对象
return children[index];
}
parents.push(parent);
children.push(child);
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
查找素数
试除法
这种方式很传统理解上也简单,给定一个范围,那么就逐个循环去试除小于它数。
现在我们假设 N 等于 120
let N = 120;
let primes = [];
// 用于存素数结果集
loop:for(let x=2;x<=N;x++){for(let k=2;k<x;k++){if(x%k==0) continue loop;
// 一旦有被小于它的数整除,则退出试下一个数
}
// 能走到这一步的就是素数了
primes.push(x);
}
console.log(primes.join(','))
筛法
先把所有 2 的倍数去掉,然后剩下的那些数里面,最小的是 3,3 就是素数,然后把 3 的倍数都去掉,剩下的数里面,最小的是 5,所以 5 也是素数…(可以看出已跳过 4 的试除,越多到后面跳过的数越多)
上述过程依次进行,但不像试除法逐个进行,就可以把某个范围内的非素数全都除去,剩下的就是素数了。这种方式的好处在于运算不重复,高效。
有一张很形象的动画,能直观地体现出筛法的工作过程。(非素数就像被筛子筛掉一样)
let N = 120;
let primes = [];
// 用于存素数结果集
let nums = [];
// 待筛选的数据集
for(let x=2;x<=N;x++){
//hooyes 提示:此处初始化的时候,也可直接筛掉 2 的倍数数据减半。//if(x%2!==0)
nums.push(x);
}
// 递归函数
function PrimeFn(data){let p = data.shift();
// 数组最前端的一个数即素数,拿出来存起,并作为下次筛除的分母。primes.push(p);
let t = [];
for(let v of data){v%p!==0 ? t.push(v) : ""
// 能被 p 整除的都筛除掉,不能整除的放到临时数组 t 存起来。}
// t 是下次待筛数组,元素个数会越来越少,若还有就进行一次递归。t.length>0 ? PrimeFn(t) : ""
}
PrimeFn(nums);
console.log(primes.join(','));
/*
得到小于 N 的素数集合
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113
*/
原文地址(https://hooyes.net/p/javascri…[https://hooyes.net/p/javascript-prime-number]
处理金额
/**
* 金额三位一划分
* @param {[string/number]} money [金额]
* @param {[string/number]} round [小数位]
* @param {[any]} flag [是否四舍五入]
* @return {[type]} [description]
*/
function formatMoney(money,round,flag) {money = Number(money);
round = Number(round);
let formatReg = /(\d)(?=(\d{3})+\.)/g;
let sliceReg = new RegExp (`([0-9]+\.[0-9]{${round}})[0-9]*`);
if(!isNaN(money)&&Object.prototype.toString.call(money).slice(8,-1) === 'Number') {if (!isNaN(round)&&flag) {return String(money.toFixed(round)).replace(formatReg,'$1,')
} else if(!isNaN(round)){return String(money).replace(sliceReg,'$1').replace(formatReg,'$1,')
} else if(round === 'undefined'){return String(money).replace(formatReg,'$1,')
} else {throw new Error('round is not Number!')
}
} else {throw new Error('money is not Number!')
}
}
let res = formatMoney('1987562.12812',3,true)
console.log(res)
最后
创建了一个前端学习交流群,感兴趣的朋友,一起来嗨呀!