这个项目是我博客,博文的放在 GitHub 的 issues 里面。
您的Star是我进行创作的动力!
- 工作当中常用到的CSS 2020-07-28
- 二叉树的深度与广度遍历 2020-11-09
- 原型与原型链 2020-11-10
- JavaScript深浅拷贝的实现 2020-12-11
- 栈的应用——JS实现括号匹配问题 2020-12-11
个人博客,相关内容写在 Issues 里面
这个项目是我博客,博文的放在 GitHub 的 issues 里面。
您的Star是我进行创作的动力!
1、原型链(proto):JS对象通过__proto__ 指向父类的原型对象,直到指向Object的原型对象为止,这样就形成了一个原型指向的链条, 即原型链。
2、原型(prototype):JS对象中都包含了一个” prototype”内部属性,这个属性所对应的就是该对象的原型。原型的主要作用就是为了实现继承与扩展对象。
3、对象的 hasOwnProperty() 来检查对象自身中是否含有该属性
4、使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回 true
5、笔试题:new 的执行过程:
// 普通函数
function Person(name){
this.name = name
}
var p = new Person("小明");
// 模拟new过程
function _new(){
// 1、创建一个新对象
let obj = {};
let [constructor, ...args] = [...arguments]; // 第一个参数是构造函数
// 2、将新对象的 __proto__ 指向构造函数的 prototype
obj.__proto__ = constructor.prototype;
// 3、使用 apply,改变构造函数 this 的指向到新对象
let result = constructor.apply(obj, args);
if(result && (typeof result == "object" || typeof result == "function")){
// 如果构造函数返回的结果是一个对象,就返回这个对象
return result
}
// 如果构造函数返回的不是一个对象,就返回创建的新对象。
return obj
}
let p2 = _new(Person, "小明")
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
a.__proto__ == A.prototype
A.prototype.__proto__ == Object.prototype
A.prototype.constructor == A
A.__proto__ == Function.prototype
Object.__proto__ == Function.prototype
Function.__proto__ == Object.__proto__
Object.prototype.__proto__ == null
最常用的继承有以下四个:
1、原型继承 // 缺点:1、创建子类实例时,不能向父类的构造函数传递参数。2、子类实例共享了父类构造函数的引用属性(如:arr)。
2、组合继承 // 缺点:两次调用父类的构造函数,造成了不必要的消耗。
3、寄生组合继承
4、ES6 的 extend 继承
// 原型继承
var Son.prototype = Object.create(Father);
// 组合继承
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
function Son(name,age){
Father.call(this,name);//第二次调用父类构造函数
this.age = age;
}
Son.prototype = new Father();//第一次调用父类构造函数
Son.prototype.constructor = Son;
// 寄生组合继承
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
function Son(name,age){
Father.call(this,name);
this.age = age;
}
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor=Son;
如下,就是一个二叉树。
1
/ \
2 3
/ \ / \
4 5 6 7
1、从上到下遍历,称为深度遍历,深度遍历又有3种遍历策略,分别是前序遍历、中序遍历、后序遍历。
-前序遍历,遍历顺序是根左右:1245367
-中序遍历,遍历顺序是左根右:4251637
-后序遍历,遍历顺序是左右根:4526731
2、按层级遍历,则称为广度遍历,它的遍历顺序是:1234567
3、遍历代码实现:
// 前序遍历
preorderTraversal = (tree) =>{
//判断树是否为空
if(tree == null) return false;
//根节点
console.log(tree.data)
//左子树
this.preorderTraversal(tree.left)
//右子树
this.preorderTraversal(tree.right)
}
// 中序遍历
inorderTraversal = (tree) =>{
//判断树是否为空
if(tree == null) return false;
//左子树
this.inorderTraversal(tree.left);
//根节点
console.log(tree.data)
//右节点
this.inorderTraversal(tree.right);
}
// 后序遍历
postorderTraversal = (tree) =>{
//判断树是否为空
if(tree == null) return false;
//左子树
this.postorderTraversal(tree.left);
//右子树
this.postorderTraversal(tree.right);
//根节点
console.log(tree.data)
}
// BFS广度遍历
function BFS(tree) {
const queue = [] // 初始化队列queue
// 根结点首先入队
queue.push(tree)
// 队列不为空,说明没有遍历完全
while(queue.length) {
const top = queue[0] // 取出队头元素
// 访问 top
console.log(top.val)
// 如果左子树存在,左子树入队
if(top.left) {
queue.push(top.left)
}
// 如果右子树存在,右子树入队
if(top.right) {
queue.push(top.right)
}
queue.shift() // 访问完毕,队头元素出队
}
}
4、面试真题
翻转二叉树
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
const invertTree = function(root) {
// 定义递归边界
if(!root) {
return root;
}
// 递归交换右孩子的子结点
let right = invertTree(root.right);
// 递归交换左孩子的子结点
let left = invertTree(root.left);
// 交换当前遍历到的两个左右孩子结点
root.left = right;
root.right = left;
return root;
};
无非就是 JSON.stringify() 和递归两种方法。
如果值是 undefined、function 等,在转换的时候会丢失,如果存在循环引用,则会出错,所以还是要比较谨慎使用 JSON.stringify 来做深拷贝。
存在循环引用的问题。问题如下:
const a = {val:2};
a.target = a; // 拷贝a会出现系统栈溢出,因为出现了无限递归的情况。
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制。
循环引用,即对象的属性直接的引用了自身的情况,解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
function deepClone(obj, map = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj; // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝 (typeof [1,2,3]输出"object")
if (map.get(obj)) return map.get(obj); // 是对象的话就要进行深拷贝
// new obj.constructor(),实例化一个同类型的空对象,如果是object则是{},如果是array则是[]
let cloneObj = new obj.constructor(); // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
map.set(obj, cloneObj); // 保存这个空对象的引用
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], map);
}
}
return cloneObj;
}
let obj = { name: '阿猫', address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
obj.arr = [1, 2, 3];
obj.fn = function(){console.log('阿猫Function')};
let cloneObj = deepClone(obj);
cloneObj.address.x = 200;
cloneObj.arr = [4, 5, 6];
cloneObj.fn = function(){console.log('阿狗Function')};
console.log(obj.address.x); // 100
console.log(cloneObj.address.x); // 200
console.log(obj.arr); // [1, 2, 3]
console.log(cloneObj.arr); // [4, 5, 6]
console.log(obj.fn()); // 阿猫Function
console.log(cloneObj.fn()); // 阿狗Function
let a1 = {name: 'pan'};
let aa1 = new a1.constructor();
console.log(aa1, typeof aa1, Object.prototype.toString.call(aa1)); // {} "object" "[object Object]"
let a2 = [1,2,3,4,5];
let aa2 = new a2.constructor();
console.log(aa2, typeof aa2, Object.prototype.toString.call(aa2)); // [] "object" "[object Array]"
let a3 = new Set([1,2,3,4]);
let aa3 = new a3.constructor();
console.log(aa3, typeof aa3, Object.prototype.toString.call(aa3)); // Set(0) {} "object" "[object Set]"
遇到这个问题的时候,我心中立马奔现出,以下这个简单粗暴的方法。。。
function isValid(str) {
let a, b, c = 0;
for (let s of str) {
if (s == "(") a++;
if (s == ")") a--;
if (s == "[") b++;
if (s == "]") b--;
if (s == "{") c++;
if (s == "}") c--;
}
return !(a || b || c);
}
后面经过自测,发现是有问题的,例如匹配这个"([ ) ]"字符串,得到的结果是true,这......还是乖乖用栈实现吧。
算法流程
function isValid(str) {
// 以左右括号来建立一个对象,key为左括号,value为右括号
let obj = {
"{": "}",
"(": ")",
"[": "]",
};
//实例化一个栈
let arr = [];
//遍历str字符串
for (let s of str) {
if (obj[s]) {
arr.push(s); //是左括号,入栈
} else if (Object.values(obj).includes(s)) {
// 右括号 将当前的元素和栈顶的第一个元素进行匹配
let top = arr.pop();
if (s !== obj[top]) return false;
} else {
//这里排除的是空字符的情况,如果不是左右括号而是其他的空字符串或者非法字符的话,将终止本次循环,执行下一次循环
continue;
}
}
//遍历完成之后要保证栈内要为空
return arr.length === 0;
}
console.log(isValid("()")); //true
console.log(isValid("([ ) ]")); //false
console.log(isValid("([{ )]")); //false
console.log(isValid("()[ ]{}")); //true
console.log(isValid(" { ]")); //false
console.log(isValid("{ [ ] }")); //true
总结:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>内容不超过屏幕,footer固定在底部,超过时被撑出屏幕</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
.wrap {
height: auto;
min-height: 100%;
text-align: center;
}
.main {
padding-bottom: 80px;
}
footer {
height: 80px;
line-height: 80px;
margin-top: -80px;
text-align: center;
}
</style>
</head>
<body>
<section class="wrap">
<div class="main">
<div>
<h1>标题</h1>
<p>我是内容。</p>
<p>是的,</p>
<p>我真的是内容。</p>
<p>我不是内容?</p>
<p>我是。</p>
<br />
<p>我没超过屏幕高度时,footer会老实待在固定在底部</p>
<p>当我超过屏幕高度时,footer会被我撑出屏幕,但始终在我下面。</p>
<p>是的,没错,就是这样。</p>
<br />
</div>
</div>
</section>
<footer>
<h1>我是footer</h1>
</footer>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>内容不超过屏幕,footer固定在底部,超过时被撑出屏幕</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
// 上中下布局(中部分自适应)
.top_middle_bottom_wrap{
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
flex-direction: column;
min-height: 100%;
}
.top_middle_bottom_wrap .top_wrap{
min-height: 45px;
}
.top_middle_bottom_wrap .middle_wrap{
flex: 1;
}
.top_middle_bottom_wrap .bottom_wrap{
min-height: 40px;
}
</style>
</head>
<body>
<div class="top_middle_bottom_wrap">
<div class="top_wrap">
<p>我是头部。</p>
</div>
<div class="middle_wrap">
<p>我是内容。</p>
</div>
<div class="bottom_wrap">
<p>我是底部。</p>
</div>
</div>
</body>
</html>
第一种方式:
.img_wrap{
display: flex;
flex-wrap: wrap;
.img_item{
font-size: 0;
flex: 0 0 108px;
margin-left: 7px;
margin-bottom: 7px;
// 选择第一个元素或者换行的第一元素(3个元素为一行)
&:nth-of-type(3n+1){
margin-left: 0;
}
}
img,
video {
width:108px;
height:108px;
}
video {
object-fit: contain;
}
}
第二种方式:
.img_wrap{
display: flex;
flex-wrap: wrap;
.img_item{
font-size: 0;
width: 25%;
margin-bottom: 7px;
}
img,
video {
width:108px;
height:108px;
}
video {
object-fit: contain;
}
}
<html>
<head>
<meta charset="utf-8">
<style>
.tags {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
.tags > span {
display: inline-block;
padding: 5px;
border: 1px solid #333;
color: #333;
}
</style>
</head>
<body>
<div class="tags">
<span>标签1</span>
<span>标签22222</span>
<span>标签333333</span>
<span>标签4</span>
<span>标签555555</span>
<span>标签666666</span>
<span>标签77777777</span>
<span>标签88888</span>
<span>标签9999999999999999</span>
</div>
</body>
</html>
<p>向右: <i class="arrows arrows_right"></i></p>
<p>向左: <i class="arrows arrows_left"></i></p>
<p>向上: <i class="arrows arrows_up"></i></p>
<p>向下: <i class="arrows arrows_down"></i></p>
.arrows {
border: solid black;
border-width: 0 1px 1px 0;
display: inline-block;
padding: 3px;
}
.arrows_right {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.arrows_left {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.arrows_up {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
margin-top: 5px;
}
.arrows_down {
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
margin-bottom: 5px;
}
// 文字超出省略号,父元素是display:flex;,则父元素需要添加overflow: hidden;即可,用法:.text-overflow(200px)
.text-overflow(@max:100%) {
max-width: @max;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.vertical_line{
position: relative;
margin-right: 24px;
&::after{
position: absolute;
right: -16px;
top: 50%;
transform: translateY(-50%);
content: '';
width:0px;
height:20px;
border:1px solid rgba(201,201,201,1);
}
&:last-child{
margin-right: 0;
&::after{
width:0;
height:0;
border:0;
margin-right: 0;
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.