CSS

1、Flex布局
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

使用Flex时,比使用inline-block和float时重排更快,在布局时优先考虑Flex。

2、css清除浮动

浮动:浮动使元素脱离文档流,可设置文字环绕,但会导致父元素的高度塌陷。

clear属性
  1. 追加元素并设置clear属性
    clear: both;
  2. 使用CSS样式插入伪元素

    .clearfix::after {
      content: "";
      display: block;
      clear: both;
    }
创建父级BFC
  1. 父元素设置overflow: hidden;
  2. 父元素浮动(float)
  3. 父元素display: inline-block;
给父元素设置高度
3、选择器优先级

!important > 行内样式 > #id > .class > tag > * > 继承 > 默认
CSS选择器从右往左解析,推荐使用BEM命名

4、BFC
BFC块级格式化上下文 (Block Formatting Context) ,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
触发条件
  1. 根元素或其它包含它的元素
  2. 浮动元素 (元素的 float 不是 none)
  3. 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
  4. 内联块 (元素具有 display: inline-block)
  5. 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
  6. 表格标题 (元素具有 display: table-caption, HTML表格标题默认属性)
  7. 具有overflow 且值不是 visible 的块元素
  8. 弹性盒(flex或inline-flex)
  9. display: flow-root
  10. column-span: all

规则
  1. 属于同一个 BFC 的两个相邻 Box 垂直排列
  2. 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
  3. BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)
  4. BFC 的区域不会与 float 的元素区域重叠
  5. 计算 BFC 的高度时,浮动子元素也参与计算
  6. 文字层不会被浮动层覆盖,环绕于周围
应用
  1. 阻止margin重叠
  2. 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
  3. 自适应两栏布局
  4. 可以阻止元素被浮动元素覆盖

    5、盒模型

    标准盒模型(content-box)和IE盒模型(border-box)。

    标准盒模型

    浏览器默认模型,即box-sizing: content-box;
    width = content-width
    height = content-height

    IE盒模型

    box-sizing: border-box;
    width = content-width + padding-width + border-width
    height = content-height + padding-height + border-height

    offsetWidth

    返回元素的宽度(包括元素宽度、内边距和边框,不包括外边距)
    offsetWidth = content-width + padding-width + border-width

    clientWidth

    元素的内部宽度(包括元素宽度、内边距,不包括边框和外边距)
    clientWidth = content-width + padding-width
    clientWidth用于获取dom元素在浏览器的实际占用文档的宽度

    6、布局

    实现左右两边固定宽度,中间自适应的布局的几种方法

    7、利用html css 编写样式,div垂直body居中、div内的text垂直居中,div高度等于body宽度的一半
    <body>
    <div>垂直居中</div>
    </body>
    
    /* 方法一 */
    body {
     position: relative;
     height: 100vh;
    }
    div {
     position: absolute;
     top: 50%;
     width: 100%;
     height: 0;
     padding: 25% 0;
     transform: translateY(-50%);
     text-align: center;
     background: green;
    }
    /* 方法二 */
    body {
     height: 100vh;
    }
    body,
    div {
     display: flex;
     align-items: center;
    }
    div {
     width: 100%;
     padding: 25% 0;
     text-align: center;
     background: green;
    }
    8、文本溢出省略效果
    /* 超出一行用省略号显示 */
    .title{
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
    /* 超出2行用省略号显示 */
    .title{
      overflow: hidden;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      text-overflow: ellipsis;
    }
    9、1像素问题

    使用css3的 scaleY(0.5) 来解决
    eg:div的border-top的1px问题解决。

    .border-top-1px {
      position: relative;
    }
    .border-top-1px::before {
      content: "";
      display: block;
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      transform-origin: 0 top;
      border-top: 1px solid #ccc;
    }
    @media (min-resolution: 2dppx) {
      .border-top-1px::before {
         transform: scaleY(.5);
      }
    }
    @media (min-resolution: 3dppx) {
      .border-top-1px::before {
         transform: scaleY(.333);
      }
    }
10、搞懂word-break、word-wrap、white-space
  1. white-space: 控制空白字符的显示,同时还能控制是否自动换行
    1) white-space: nowrap; // 不换行
    2) white-space: pre-wrap; // 保留空格和换行符,且可以自动换行
    3) white-space: pre-line; // 空格被合并,换行符存在
  2. word-break: 控制单词如何被拆分换行
    1) word-break: keep-all; // 所有“单词”一律不拆分换行。注意: “单词”包括连续的中文字符(还有日文、韩文等),或者可以理解为只有空格可以触发自动换行
    2) word-break: break-all; // 所有单词碰到边界一律拆分换行。(不管是incomprehensibilities这样一行都显示不下的单词,还是long这样很短的单词,只要碰到边界,都会被强制拆分换行。用word-break: break-all时要慎重。)
  3. word-wrap(overflow-wrap): 控制长度超过一行的单词是否被拆分换行
    1) word-wrap: break-word; // 只有当一个单词一整行都显示不下时,才会拆分换行该单词。

JS

变量类型和计算

1、typeof能判断哪些类型
  1. 识别所有值类型:undefined、String、Number、Boolean、Symbol、BigInt
  2. 识别函数:Function
  3. 判断是否是引用类型(但不可再细分)

    // 判断函数
    typeof function () {}  // function
    typeof null;   // object
    typeof [1, 2];  // object
2、何时使用 === 何时使用 ==

除了 == null 之外,其他都一律用 ===
obj.a == null 等价于 obj.a === null || obj.a === undefined的简写形式

3、值类型和引用类型的区别

1) 存储
值类型:在栈中存储
引用类型:在堆和栈中存储 // 栈中存储的是引用类型的内存地址

2) 常见值类型
undefined、String、Number、Boolean、Symbol、BigInt

3) 常见引用类型
Object、Array、null、Function
null:特殊引用类型,指针指向为空地址
function fn() {} // 特殊引用类型,但不用于存储数据,所以没有"拷贝、复制函数"这一说

4、手写深拷贝
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
  if (typeof obj !== 'object' || obj == null) {
    return obj; // obj是null, 或者不是对象和数组,直接返回
  }

  // 初始化返回结果
  let result = obj instanceof Array ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) { // 保证key不是原型的属性
      result[key] = deepClone(obj[key]);  // 递归
    }
  }
  // 返回结果
  return result;
}
5、变量计算 - 类型转换

1) 字符串拼接

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

2) ==

100 == '100';  // true
0 == '';  // true
0 == false; // true
false == ''; // true
null == undefined; // true

3) if语句和逻辑运算

  1. truly变量:!!a === true的变量
  2. falsely变量:!!a === false的变量。0, NaN, '', null, undefined, false
6、JS中哪些内置函数 -- 数据封装类对象

Object、Array、Boolean、Number、String、Function、Date、RegExp、Error

7、如何理解JSON

JSON是一种数据格式,一个JS对象。
JSON.stringify({a: 100, b: 20}); // 把对象变成字符串
JSON.parse('{"a": 100, "b": 20}'); // 把字符串变成对象

原型和原型链

1、如何判断一个变量是不是数组

a instanceof Array // instanceof用于判断引用类型属于哪个构造函数的方法
f instanceof Foo的判断逻辑是:

  1. f的__proto__一层一层往上,能否对应到Foo.prototype
  2. 再试着判断f instanceof Object

    [] instanceof Array;  // true
    [] instanceof Object; // true
    {} instanceof Object; // true
    2、手写一个简易的jQuery,考虑插件和扩展性
    class jQuery {
      constructor(selector) {
     const result = document.querySelectorAll(selector);   // dom查询
     const length = result.length;  // 类数组
     for (let i = 0; i < length; i++) {
        this[i] = result[i];
     }
     this.length = length;
     this.selector = selector;
      }
    
      get(index) {
     return this[index];
      }
    
      each(fn) {
     for (let i = 0; i < this.length; i++) {
        const elem = this[i];
        fn(elem);
      }
    }
    
      on(type, fn) {
      return this.each(elem => {
        elem.addEventListener(type, fn, false);
      })
      }
    
      // 扩展更多 DOM API
    }
    
    // 插件
    jQuery.prototype.dialog = function(info) {
      alert(info);
    }
    
    // 造轮子
    class myJQuery extends jQuery { // 继承
      constructor(selector) {
     super(selector);
      }
      // 扩展自己的方法
      addClass(className) {
     // dosth
      }
    }
    3、描述new一个对象的过程
  3. 创建一个新对象
  4. this指向这个新对象
  5. 执行代码,即对this赋值
  6. 返回this // return this
4、描述new一个对象的过程
// 父类
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`${this.name} say sth`);
  }
}
// 子类
class Student extends Person {
  constructor(name, number) {
    super(name);
    this.number = number;
  }
  toSchool() {
    console.log(`姓名 ${this.name}, 学号 ${this.number}`);
  }
}

class Teacher extends Person {
  constructor(name, major) {
    super(name);
    this.major = major;
  }

  teach() {
    console.log(`${this.name} 教授 ${this.major}`);
  }
}

const lily = new Student('莉莉', 7); // 实例
console.log(lily.name, lily.number);
原型

class 实际上是函数,是语法糖
typeof Person; // function
typeof Student; // function

隐式原型和显示原型
lily.__proto__ // 隐式原型 Person
Student.prototype // 显示原型 Person
lily.__proto__ === Student.prototype // true

原型关系
  1. 每个class都有显示原型prototype
  2. 每个实例都有隐式原型__proto__
  3. 实例的__proto__指向对应class的prototype
基于原型的执行规则
  1. 获取属性lily.name 或执行方法lily.sayHi()时
  2. 先在自身属性和方法寻找
  3. 如果找不到则自动去__proto__中查找
原型链

Student.prototype.__proto__ // Person.prototype
Person.prototype === Student.prototype.__proto__

原型规则
  1. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了"null"以外)。
  2. 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象。// __proto__: 隐式原型
  3. 所有的函数,都有一个prototype属性,属性值也是一个普通的对象。 // prototype: 显示原型
  4. 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的"prototype"属性值。
  5. 当试图得到一个对象的某个属性时,若该对象本身没有该属性,则会去它的__proto__(即它的构造函数的prototype)中寻找。

作用域和闭包

1、this的不同应用场景,如何取值

this的取值在函数执行时确定
1) 当做普通函数被调用(this的值是window)
2) 使用call、apply、bind 被调用
3) 作为对象方法被调用
4) 在class方法中调用
5) 箭头函数: 箭头函数的this取其上级作用域的this值

2、手写bind函数
Function.prototype.bind1 = function() {
  // 将参数解析为数组
  const args = Array.prototype.slice.call(arguments); 
  // 获取this(取出数组第一项,数组剩余的就是传递的参数)
  const t = args.shift(); // 调用时bind参数的第一项,即this的值
  const _this = this; // 当前函数, eg: fn1.bind(...) 中的 fn1
  // 返回一个函数
  return function() {
    // 执行原函数,并返回结果
    return _this.apply(t, args);
  }
}
3、作用域和自由变量

1) 作用域:变量的使用范围

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域(ES6新增)

2) 自由变量:

  1. 一个变量在当前作用域没有定义,但被使用了
  2. 向上级作用域,一层一层依次寻找,直至找到为止
  3. 如果到了全局作用域都没找到,则报错 xx is not defined
4、闭包

闭包:作用域应用的特殊情况,有两种表现:

  1. 函数作为参数被传递
  2. 函数作为返回值被返回

    // 函数作为参数
    function print(fn) {
      let a = 200;
      fn();
    }
    let a = 100;
    function fn() {
      console.log(a);
    }
    print(fn); // 100
    
    // 函数作为返回值
    function create() {
      let a = 100;
      return function() {
     console.log(a);
      }
    }
    let fn = create();
    let a = 200;
    fn();  // 100

    所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
    闭包的影响:变量会常驻内存,得不到释放。闭包不要乱用

5、实际开发中闭包的应用场景,举例说明

1) 隐藏数据
2) 做一个简单的cache工具

// 闭包隐藏数据,只提供API
function createCache() {
   const data = {}; // 闭包中的数据,被隐藏,不被外界访问
   return {
     set(key, val) {
       data[key] = val;
     },
     get(key) {
       return data[key];
     }
   }
}

const c = createCache();
c.set('a', 100);
console.log(c.get('a'));

异步

1、单线程和异步
  1. JS是单线程语言,只能同时做一件事
  2. 浏览器和node.js已经支持JS启动进程,如Web Worker
  3. JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
  4. 遇到等待(网络请求,定时任务)不能卡住
  5. 需要异步
  6. 回调callback函数形式

2) 同步和异步的区别

  1. 异步基于JS是单线程语言
  2. 异步不会阻塞代码执行
  3. 同步会阻塞代码执行
2、手写Promise加载一张图片
function loadImg(src) {
   const promise = new Promise((resolve, reject)  => {
       const img = document.createElement('img');
       img.onload = () =>  {
          resolve(img);
       }
       img.onerror = () => {
           const err = new Error(`图片加载失败${src}`);
           reject(err);
       }
       img.src = src;
   });

   return promise;
}

const url = 'xxx.png';
loadImg(url).then(img => {
   console.log(img.width);
   return img;
}).then(img => {
   console.log(img.height);
}).catch(err => {
   console.error(err);
})
3、前端使用异步的场景有哪些
  1. 网络请求,如ajax、图片加载
  2. 定时任务,如setTimeout
4、callback hell(回调地狱) 和 Promise

1) callback hell

// 获取第一份数据
$.get(url1, (data1) => {
   console.log(data1);

   // 获取第二份数据
   $.get(url2, (data2) => {
       console.log(data2);

       // more data
       ...
    })
})

2) Promise

function getData(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            success(data) {
                resolve(data);
            },
            error(err) {
                reject(err);
            }
        })
    })
 }

const url1 = '/data1.json';
const url2 = '/data2.json';
const url3 = '/data3.json';

getData(url1).then(data1 => {
    console.log(data1);
    return getData(url2);
}).then(data2 => {
    console.log(data2);
    return getData(url3);
}).then(data3 => {
    console.log(data3);
}).catch(err => {
    console.error(err);
})

DOM

1、DOM性能
  1. DOM操作非常"昂贵", 避免频繁的DOM操作
  2. 对DOM查询做缓存

    const pList = document.getElementsByTagName('p'); // 缓存DOM查询
    const pLength = pList.length;
    for (let i = 0; i < pLength; i++) {
     // 缓存length,只进行一次DOM查询
    }
  3. 将频繁操作改为一次性操作

    const list = document.getElementById('list');
    // 创建一个文档片段,此时还没有插入到DOM树中
    const frag = document.createDocumentFragment();
    
    // 执行插入
    for (let i = 0; i < 10; i++) {
     const li = document.createElement('li');
     li.innerHTML = 'item ' + i;
     frag.appendChild(li);
    }
    
    // 都完成之后,再插入到DOM树中
    list.appendChild(frag);

事件绑定

1、编写一个通用的事件监听函数
function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector;
        selector = null;
    }
    elem.addEventListener(type, e => {
        const target = e.target;
        if (selector) {  // 需要代理
            if (target.matches(selector)) {
                fn.call(target, e);
            }
        } else {
            fn.call(target, e);  // 不需要代理
        }
    })
}
2、事件代理
  1. 代码简洁
  2. 减少浏览器内存占用
  3. 不要滥用

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
事件捕获(event capturing): 当鼠标点击或者触发dom事件时(被触发dom事件的这个元素被叫作事件源),浏览器会从根节点到事件源(由外到内)进行事件传播。
事件冒泡(dubbed bubbling):事件冒泡刚好相反,事件源到根节点(由内到外)进行事件传播。
DOM标准事件流的触发的先后顺序为:先捕获再冒泡,即当触发DOM事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。
element.addEventListener(event, function, useCapture);
将多个子元素上相同的事件添加到父元素上,eg: 将li的事件代理到ul上。

3、无限下拉的图片列表,如何监听每个图片的点击
  1. 事件代理
  2. 用e.target获取触发元素
  3. 用matches来判断是否是触发元素

ajax

1、手写一个简易的ajax
function ajax(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                } else if (xhr.status === 404){
                    reject(new Error('404 not found'));
                }
            }
        }
        xhr.send(null);
    })
    return p;
}

ajax(url).then(res => {
    console.log(res);
}).catch(err => {
    console.error(err);
})
2、XMLHttpRequest

1) get请求

const xhr = new XMLHttpRequest();
xhr.open('GET', '/api', true);
xhr.onreadystatechange = function() {
    // 异步执行
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(xhr.responseText);
        }
    }
}
xhr.send(null);

2) post请求

const xhr = new XMLHttpRequest();
xhr.open('POST', '/login', true);
xhr.onreadystatechange = function() {
    // 异步执行
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(xhr.responseText);
        }
    }
}
const params = {
    userName: 'liz',
    pwd: '1234'
}
xhr.send(JSON.stringify(params));
3、状态码

1) xhr.readyState
0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部相应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用

2) xhr.status
2xx - 表示成功处理请求,如200
3xx - 需要重定向,浏览器直接跳转,如301 302 304
301 - 永久重定向
302 - 临时重定向
304 - 资源未改变,浏览器利用缓存的资源
4xx - 客户端请求错误,如404 403
404 - 请求地址有错误
403 - 客户端没有权限
5xx - 服务器端错误

4、跨域

浏览器的同源策略(服务端没有同源策略)
1) 什么是跨域(同源策略)

  1. ajax请求时,浏览器要求当前网页和server必须同源(安全)
  2. 同源:协议、域名、端口,三者必须一致
  3. 加载图片、css、js可无视同源策略
    <img src="跨域的图片地址" />
    <link href="跨域的css地址" /> eg: 引用cdn的地址
    <script src="跨域的js地址"></script>

<img />可用于统计打点,可使用第三方统计服务
<link />、<script>可使用CDN,CDN一般都是外域
<script>可实现JSONP

  1. 跨域
    所有的跨域都必须经过server端允许和配合
    未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

2) JSONP

  1. <script>可绕过跨域限制
  2. 服务器可以任意动态拼接数据返回
  3. 所以,<script>就可以获得跨域的数据,只要服务端愿意返回

3) CORS(服务端支持)
服务器端设置http header
response.setHeader('Access-Control-Allow-Origin', '允许跨域的域名称');
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With');
response.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');

// 接收跨域的cookie
response.setHeader('Access-Control-Allow-Credentials', 'true');

5、跨域的常用实现方式

跨域指浏览器不能执行其他网站的脚本。跨域是浏览器的同源策略限制造成的,是浏览器对js施加的安全限制。(同源策略限制)

JSONP: 利用<script>标签不受跨域限制的特点,缺点是只能支持 get 请求
function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script');
  script.src = url;
  script.async = true;
  script.type = 'text/javascript';
  window[jsonpCallback] = function(data) {
       success && success(data);
  }
  document.body.appendChild(script);
}

JSONP跨域的基本原理:由于script 标签不受浏览器同源策略的影响,允许跨域引用资源。因此,通过动态创建 script 标签,然后利用 src 属性进行跨域。

设置 CORS:

Access-Control-Allow-Origin:* // CORS(跨域资源共享): 服务端设置Access-Control-Allow-Origin

postMessage

window.postMessage() 是HTML5的一个API,专注实现不同窗口不同页面的跨域通讯。

其他

1、JS开发技巧
格式化金钱
formatRMB(num) {
   return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
最快捷的数组求最大值
const arr = [ 1,5,1,7,5,9];
Math.max(...arr)  // 9
最短的数组去重写法
[...new Set([2,"12",2,12,1,2,1,6,12,13,6])]
// [2, "12", 12, 1, 6, 13]
2、判断if([] == false) {} , if({} == false) {} , if([]) {}

若使用===,表示绝对相等,需要类型相同并且值相同;若使用==,则在类型不同的情况下会进行类型转换,然后再比较。
JS“假值”总共只有6个:false,undefined,null,0,''(空字符串),NaN

  1. []直接用于if判断条件时会被转化为true。
  2. 与布尔值比较,都会将两边的值转化为Number,[]转换为0,{}转换为NaN。
    1) [] == false // true
    2) {} == false // false
    3) [] // true
    尽量避免使用 ==!= 运算符
3、window的onload事件和domcontentloaded谁先谁后?

页面加载事件:DOMContentLoaded和onLoad
1) DOMContentLoaded:当DOM树生成后触发该事件。
2) onLoad:当页面的所有资源(css、js、图片、视频等)都加载完成后触发。

DOM完整的解析过程:

  1. 解析HTML结构。
  2. 加载外部脚本和样式表文件。
  3. 解析并执行脚本代码。// js之类的
  4. DOM树构建完成。// DOMContentLoaded事件会被触发
  5. 加载图片等外部文件。
  6. 页面加载完毕。//load事件会被触发

// todo:

参考文章:
Flex 布局教程:语法篇
中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)
CSS盒模型与style.width,offsetWidth,clientWidth的关系
字节跳动最爱考的前端面试题:CSS 基础
[浅谈JavaScript空数组[]和空对象{}的布尔运算](https://blog.csdn.net/zhouziy...


地瓜哥
16 声望0 粉丝

keep learning