znhDT0p4

znhDT0p4 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

znhDT0p4 赞了文章 · 1月10日

前端面试js高频手写大全

介绍

在前端面试中,手撕代码显然是不可避免的,并且占很大的一部分比重。

一般来说,如果代码写的好,即使理论知识答得不够清楚,也能有大概率通过面试。并且其实很多手写往往背后就考察了你对相关理论的认识。

编程题主要分为这几种类型:

* 算法题
* 涉及js原理的题以及ajax请求
* 业务场景题: 实现一个具有某种功能的组件
* 其他(进阶,对计算机综合知识的考察,考的相对较少):实现订阅发布者模式;分别用面向对象编程,面向过程编程,函数式编程实现把大象放进冰箱等等

其中前两种类型所占比重最大。
算法题建议养成每天刷一道leetcode的习惯,重点刷数据结构(栈,链表,队列,树),动态规划,DFS,BFS

本文主要涵盖了第二种类型的各种重点手写。

建议优先掌握

  • instanceof (考察对原型链的理解)
  • new (对创建对象实例过程的理解)
  • call&apply&bind (对this指向的理解)
  • 手写promise (对异步的理解)
  • 手写原生ajax (对ajax原理和http请求方式的理解,重点是get和post请求的实现)
  • 其他:数组,字符串的api的实现,难度相对较低。只要了解数组,字符串的常用方法的用法,现场就能写出来个大概。(ps:笔者认为数组的reduce方法比较难,这块有余力可以单独看一些,即使面试没让你实现reduce,写其他题时用上它也是很加分的)

话不多说,直接开始

1. 手写instanceof

instanceof作用:

判断一个实例是否是其父类或者祖先类型的实例。

instanceof在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype查找失败,返回 false

 let myInstanceof = (target,origin) => {
     while(target) {
         if(target.__proto__===origin.prototype) {
            return true
         }
         target = target.__proto__
     }
     return false
 }
 let a = [1,2,3]
 console.log(myInstanceof(a,Array));  // true
 console.log(myInstanceof(a,Object));  // true

2. 实现数组的map方法

数组的map() 方法会返回一个新的数组,这个新数组中的每个元素对应原数组中的对应位置元素调用一次提供的函数后的返回值。

用法:

const a = [1, 2, 3, 4];
const b = array1.map(x => x * 2);
console.log(b);   // Array [2, 4, 6, 8]

原生实现:

 Array.prototype.myMap = function(fn, thisValue) {
     let res = []
     thisValue = thisValue||[]
     let arr = this
     for(let i in arr) {
        res.push(fn(arr[i]))
     }
     return res
 }

3. reduce实现数组的map方法

利用数组内置的reduce方法实现map方法,考察对reduce原理的掌握

Array.prototype.myMap = function(fn,thisValue){
     var res = [];
     thisValue = thisValue||[];
     this.reduce(function(pre,cur,index,arr){
         return res.push(fn.call(thisValue,cur,index,arr));
     },[]);
     return res;
}
​
var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){
 console.log(item,index,arr);
})

4. 手写数组的reduce方法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法

参数:

  • callback(一个在数组中每一项上调用的函数,接受四个函数:)

    • previousValue(上一次调用回调函数时的返回值,或者初始值)
    • currentValue(当前正在处理的数组元素)
    • currentIndex(当前正在处理的数组元素下标)
    • array(调用reduce()方法的数组)
  • initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值)
 function reduce(arr, cb, initialValue){
     var num = initValue == undefined? num = arr[0]: initValue;
     var i = initValue == undefined? 1: 0
     for (i; i< arr.length; i++){
        num = cb(num,arr[i],i)
     }
     return num
 }
 
 function fn(result, currentValue, index){
     return result + currentValue
 }
 
 var arr = [2,3,4,5]
 var b = reduce(arr, fn,10) 
 var c = reduce(arr, fn)
 console.log(b)   // 24

5. 数组扁平化

数组扁平化就是把多维数组转化成一维数组

1. es6提供的新方法 flat(depth)

let a = [1,[2,3]]; 
a.flat(); // [1,2,3] 
a.flat(1); //[1,2,3]

其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。 depth的值设置为Infinity。

let a = [1,[2,3,[4,[5]]]]; 
a.flat(Infinity); // [1,2,3,4,5]  a是4维数组

2. 利用cancat

function flatten(arr) {
     var res = [];
     for (let i = 0, length = arr.length; i < length; i++) {
     if (Array.isArray(arr[i])) {
     res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
     //res.push(...flatten(arr[i])); //或者用扩展运算符 
     } else {
         res.push(arr[i]);
       }
     }
     return res;
 }
 let arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

6. 函数柯里化

柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。

当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?

有两种思路:

  1. 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
  2. 在调用柯里化工具函数时,手动指定所需的参数个数

将这两点结合一下,实现一个简单 curry 函数:


/**
 * 将函数柯里化
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数,默认为原函数的形参个数
 */
function curry(fn,len = fn.length) {
 return _curry.call(this,fn,len)
}
​
/**
 * 中转函数
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数
 * @param args  已接收的参数列表
 */
function _curry(fn,len,...args) {
    return function (...params) {
         let _args = [...args,...params];
         if(_args.length >= len){
             return fn.apply(this,_args);
         }else{
          return _curry.call(this,fn,len,..._args)
         }
    }
}

我们来验证一下:

let _fn = curry(function(a,b,c,d,e){
 console.log(a,b,c,d,e)
});
​
_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

我们常用的工具库 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通过占位符的方式来改变传入参数的顺序。

比如说,我们传入一个占位符,本次调用传递的参数略过占位符, 占位符所在的位置由下次调用的参数来填充,比如这样:

直接看一下官网的例子:

img

接下来我们来思考,如何实现占位符的功能。

对于 lodash 的 curry 函数来说,curry 函数挂载在 lodash 对象上,所以将 lodash 对象当做默认占位符来使用。

而我们的自己实现的 curry 函数,本身并没有挂载在任何对象上,所以将 curry 函数当做默认占位符

使用占位符,目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用了占位符,并且记录占位符所代表的参数位置。

直接上代码:


/**
 * @param  fn           待柯里化的函数
 * @param  length       需要的参数个数,默认为函数的形参个数
 * @param  holder       占位符,默认当前柯里化函数
 * @return {Function}   柯里化后的函数
 */
function curry(fn,length = fn.length,holder = curry){
 return _curry.call(this,fn,length,holder,[],[])
}
/**
 * 中转函数
 * @param fn            柯里化的原函数
 * @param length        原函数需要的参数个数
 * @param holder        接收的占位符
 * @param args          已接收的参数列表
 * @param holders       已接收的占位符位置列表
 * @return {Function}   继续柯里化的函数 或 最终结果
 */
function _curry(fn,length,holder,args,holders){
 return function(..._args){
 //将参数复制一份,避免多次操作同一函数导致参数混乱
 let params = args.slice();
 //将占位符位置列表复制一份,新增加的占位符增加至此
 let _holders = holders.slice();
 //循环入参,追加参数 或 替换占位符
 _args.forEach((arg,i)=>{
 //真实参数 之前存在占位符 将占位符替换为真实参数
 if (arg !== holder && holders.length) {
     let index = holders.shift();
     _holders.splice(_holders.indexOf(index),1);
     params[index] = arg;
 }
 //真实参数 之前不存在占位符 将参数追加到参数列表中
 else if(arg !== holder && !holders.length){
     params.push(arg);
 }
 //传入的是占位符,之前不存在占位符 记录占位符的位置
 else if(arg === holder && !holders.length){
     params.push(arg);
     _holders.push(params.length - 1);
 }
 //传入的是占位符,之前存在占位符 删除原占位符位置
 else if(arg === holder && holders.length){
    holders.shift();
 }
 });
 // params 中前 length 条记录中不包含占位符,执行函数
 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
 return fn.apply(this,params);
 }else{
 return _curry.call(this,fn,length,holder,params,_holders)
 }
 }
}

验证一下:;

let fn = function(a, b, c, d, e) {
 console.log([a, b, c, d, e]);
}
​
let _ = {}; // 定义占位符
let _fn = curry(fn,5,_);  // 将函数柯里化,指定所需的参数个数,指定所需的占位符
​
_fn(1, 2, 3, 4, 5);                 // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1);              // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2);              // print: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5);         // print: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5);        // print: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5);        // print: 1,2,3,4,5

至此,我们已经完整实现了一个 curry 函数~~

7. 实现深拷贝

浅拷贝和深拷贝的区别:

浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用

深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象

ES6浅拷贝方法:Object.assign(target,...sources)


let obj={
 id:1,
 name:'Tom',
 msg:{
 age:18
 }
}
let o={}
//实现深拷贝  递归    可以用于生命游戏那个题对二维数组的拷贝,
//但比较麻烦,因为已知元素都是值,直接复制就行,无需判断
function deepCopy(newObj,oldObj){
     for(var k in oldObj){
         let item=oldObj[k]
         //判断是数组?对象?简单类型?
         if(item instanceof Array){
             newObj[k]=[]
             deepCopy(newObj[k],item)
         }else if(item instanceof Object){
             newObj[k]={}
             deepCopy(newObj[k],item)
         }else{  //简单数据类型,直接赋值
             newObj[k]=item
         }
     }
}

8. 手写call, apply, bind

手写call

Function.prototype.myCall=function(context=window){  // 函数的方法,所以写在Fuction原型对象上
 if(typeof this !=="function"){   // 这里if其实没必要,会自动抛出错误
    throw new Error("不是函数")
 }
 const obj=context||window   //这里可用ES6方法,为参数添加默认值,js严格模式全局作用域this为undefined
 obj.fn=this      //this为调用的上下文,this此处为函数,将这个函数作为obj的方法
 const arg=[...arguments].slice(1)   //第一个为obj所以删除,伪数组转为数组
 res=obj.fn(...arg)
 delete obj.fn   // 不删除会导致context属性越来越多
 return res
}
//用法:f.call(obj,arg1)
function f(a,b){
 console.log(a+b)
 console.log(this.name)
}
let obj={
 name:1
}
f.myCall(obj,1,2) //否则this指向window

obj.greet.call({name: 'Spike'}) //打出来的是 Spike

手写apply(arguments[this, [参数1,参数2.....] ])

Function.prototype.myApply=function(context){  // 箭头函数从不具有参数对象!!!!!这里不能写成箭头函数
 let obj=context||window
 obj.fn=this
 const arg=arguments[1]||[]    //若有参数,得到的是数组
 let res=obj.fn(...arg)
 delete obj.fn
 return res
} 
function f(a,b){
 console.log(a,b)
 console.log(this.name)
}
let obj={
 name:'张三'
}
f.myApply(obj,[1,2])  //arguments[1]

手写bind

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school){
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家里蹲大学'
}
var result = bar.bind(foo, 'An') //预置了部分参数'An'
result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

简单版本

Function.prototype.bind = function(context, ...outerArgs) {
 var fn = this;
 return function(...innerArgs) {   //返回了一个函数,...rest为实际调用时传入的参数
 return fn.apply(context,[...outerArgs, ...innerArgs]);  //返回改变了this的函数,
 //参数合并
 }
}

new失败的原因:

例:

// 声明一个上下文
let thovino = {
 name: 'thovino'
}
​
// 声明一个构造函数
let eat = function (food) {
 this.food = food
 console.log(`${this.name} eat ${this.food}`)
}
eat.prototype.sayFuncName = function () {
 console.log('func name : eat')
}
​
// bind一下
let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat('orange')  //实际上orange放到了thovino里面
console.log('instance:', instance) // {}

生成的实例是个空对象

new操作符执行时,我们的thovinoEat函数可以看作是这样:

function thovinoEat (...innerArgs) {
 eat.call(thovino, ...outerArgs, ...innerArgs)
}

在new操作符进行到第三步的操作thovinoEat.call(obj, ...args)时,这里的obj是new操作符自己创建的那个简单空对象{},但它其实并没有替换掉thovinoEat函数内部的那个上下文对象thovino。这已经超出了call的能力范围,因为这个时候要替换的已经不是thovinoEat函数内部的this指向,而应该是thovino对象。

换句话说,我们希望的是new操作符将eat内的this指向操作符自己创建的那个空对象。但是实际上指向了thovinonew操作符的第三步动作并没有成功

可new可继承版本

Function.prototype.bind = function (context, ...outerArgs) {
 let that = this;
​
function res (...innerArgs) {
     if (this instanceof res) {
         // new操作符执行时
         // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
         that.call(this, ...outerArgs, ...innerArgs)
     } else {
         // 普通bind
         that.call(context, ...outerArgs, ...innerArgs)
     }
     }
     res.prototype = this.prototype //!!!
     return res
}

9. 手动实现new

new的过程文字描述:

  1. 创建一个空对象 obj;
  2. 将空对象的隐式原型(proto)指向构造函数的prototype。
  3. 使用 call 改变 this 的指向
  4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。
function Person(name,age){
 this.name=name
 this.age=age
}
Person.prototype.sayHi=function(){
 console.log('Hi!我是'+this.name)
}
let p1=new Person('张三',18)
​
////手动实现new
function create(){
 let obj={}
 //获取构造函数
 let fn=[].shift.call(arguments)  //将arguments对象提出来转化为数组,arguments并不是数组而是对象    !!!这种方法删除了arguments数组的第一个元素,!!这里的空数组里面填不填元素都没关系,不影响arguments的结果      或者let arg = [].slice.call(arguments,1)
 obj.__proto__=fn.prototype
 let res=fn.apply(obj,arguments)    //改变this指向,为实例添加方法和属性
 //确保返回的是一个对象(万一fn不是构造函数)
 return typeof res==='object'?res:obj
}
​
let p2=create(Person,'李四',19)
p2.sayHi()

细节:

[].shift.call(arguments)  也可写成:
 let arg=[...arguments]
 let fn=arg.shift()  //使得arguments能调用数组方法,第一个参数为构造函数
 obj.__proto__=fn.prototype
 //改变this指向,为实例添加方法和属性
 let res=fn.apply(obj,arg)

10. 手写promise(常见promise.all, promise.race)

// Promise/A+ 规范规定的三种状态
const STATUS = {
 PENDING: 'pending',
 FULFILLED: 'fulfilled',
 REJECTED: 'rejected'
}
​
class MyPromise {
 // 构造函数接收一个执行回调
 constructor(executor) {
     this._status = STATUS.PENDING // Promise初始状态
     this._value = undefined // then回调的值
     this._resolveQueue = [] // resolve时触发的成功队列
     this._rejectQueue = [] // reject时触发的失败队列
    ​
 // 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
 const resolve = value => {
     const run = () => {
         // Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
         if (this._status === STATUS.PENDING) {
             this._status = STATUS.FULFILLED // 更改状态
             this._value = value // 储存当前值,用于then回调
            ​
             // 执行resolve回调
             while (this._resolveQueue.length) {
                 const callback = this._resolveQueue.shift()
                 callback(value)
             }
         }
     }
     //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
     setTimeout(run)
 }
​
 // 同 resolve
 const reject = value => {
     const run = () => {
         if (this._status === STATUS.PENDING) {
         this._status = STATUS.REJECTED
         this._value = value
        ​
         while (this._rejectQueue.length) {
             const callback = this._rejectQueue.shift()
             callback(value)
         }
     }
 }
     setTimeout(run)
 }

     // new Promise()时立即执行executor,并传入resolve和reject
     executor(resolve, reject)
 }
​
 // then方法,接收一个成功的回调和一个失败的回调
 function then(onFulfilled, onRejected) {
  // 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
  typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  typeof onRejected !== 'function' ? onRejected = error => error : null

  // then 返回一个新的promise
  return new MyPromise((resolve, reject) => {
    const resolveFn = value => {
      try {
        const x = onFulfilled(value)
        // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
  }
}
​
  const rejectFn = error => {
      try {
        const x = onRejected(error)
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }

    switch (this._status) {
      case STATUS.PENDING:
        this._resolveQueue.push(resolveFn)
        this._rejectQueue.push(rejectFn)
        break;
      case STATUS.FULFILLED:
        resolveFn(this._value)
        break;
      case STATUS.REJECTED:
        rejectFn(this._value)
        break;
    }
 })
 }
 catch (rejectFn) {
  return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
  return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
    MyPromise.resolve(callback()).then(() => error)
  })
}

 // 静态resolve方法
 static resolve(value) {
      return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

 // 静态reject方法
 static reject(error) {
      return new MyPromise((resolve, reject) => reject(error))
    }

 // 静态all方法
 static all(promiseArr) {
      let count = 0
      let result = []
      return new MyPromise((resolve, reject) =>       {
        if (!promiseArr.length) {
          return resolve(result)
        }
        promiseArr.forEach((p, i) => {
          MyPromise.resolve(p).then(value => {
            count++
            result[i] = value
            if (count === promiseArr.length) {
              resolve(result)
            }
          }, error => {
            reject(error)
          })
        })
      })
    }

 // 静态race方法
 static race(promiseArr) {
      return new MyPromise((resolve, reject) => {
        promiseArr.forEach(p => {
          MyPromise.resolve(p).then(value => {
            resolve(value)
          }, error => {
            reject(error)
          })
        })
      })
    }
}

11. 手写原生AJAX

步骤

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 服务器返回 XML 格式的字符串
  4. JS 解析 XML,并更新局部页面

    不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON

了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

version 1.0:

myButton.addEventListener('click', function () {
  ajax()
})

function ajax() {
  let xhr = new XMLHttpRequest() //实例化,以调用方法
  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步
  xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。
    if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。
      if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功
        let string = request.responseText
        //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}

promise实现

function ajax(url) {
  const p = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject('请求出错')
        }
      }
    }
    xhr.send()  //发送hppt请求
  })
  return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

12. 手写节流防抖函数

函数节流与函数防抖都是为了限制函数的执行频次,是一种性能优化的方案,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。

节流:连续触发事件但是在 n 秒中只执行一次函数

例:(连续不断动都需要调用时用,设一时间间隔),像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多。

防抖:指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

例:(连续不断触发时不调用,触发完后过一段时间调用),像仿百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

防抖的实现:

function debounce(fn, delay) {
     if(typeof fn!=='function') {
        throw new TypeError('fn不是函数')
     }
     let timer; // 维护一个 timer
     return function () {
         var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
         var args = arguments;
         if (timer) {
            clearTimeout(timer);
         }
         timer = setTimeout(function () {
            fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
         }, delay);
     };
}

// 调用​
input1.addEventListener('keyup', debounce(() => {
 console.log(input1.value)
}), 600)

节流的实现:

function throttle(fn, delay) {
  let timer;
  return function () {
    var _this = this;
    var args = arguments;
    if (timer) {
      return;
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 这里args接收的是外边返回的函数的参数,不能用arguments
      // fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
      timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
    }, delay)
  }
}

div1.addEventListener('drag', throttle((e) => {
  console.log(e.offsetX, e.offsetY)
}, 100))

13. 手写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)
)

14. 函数实现一秒钟输出一个数

for(let i=0;i<=10;i++){   //用var打印的都是11
 setTimeout(()=>{
    console.log(i);
 },1000*i)
}

15. 创建10个标签,点击的时候弹出来对应的序号?

var a
for(let i=0;i<10;i++){
 a=document.createElement('a')
 a.innerHTML=i+'<br>'
 a.addEventListener('click',function(e){
     console.log(this)  //this为当前点击的<a>
     e.preventDefault()  //如果调用这个方法,默认事件行为将不再触发。
     //例如,在执行这个方法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。我们可以用 event.isDefaultPrevented() 来确定这个方法是否(在那个事件对象上)被调用过了。
     alert(i)
 })
 const d=document.querySelector('div')
 d.appendChild(a)  //append向一个已存在的元素追加该元素。
}

参考:

数组扁平化 https://juejin.im/post/5c971ee16fb9a070ce31b64e#heading-3

函数柯里化 https://juejin.im/post/6844903882208837645

节流防抖 https://www.jianshu.com/p/c8b...

查看原文

赞 41 收藏 36 评论 2

znhDT0p4 收藏了文章 · 2020-12-19

字节跳动前端实习面经(三轮技术面+hr面)2020.12

个人背景

本人研二,科班,学前端时间四个月左右,有做过react小程序,但是对react的理解不是很深。有一段四个月的前端实习经历,用的是vue。投的字节跳动北京的base。

一面 60min

最近在学什么?node.js,为什么要学node.js,讲一下你学到node.js的哪些知识

常见数据类型?
ES6有哪些数据类型,set和map区别?set放NaN会有几个?NaN全等于NaN吗?答不全等,问那为什么set里面只能放一个

问输出,考察this指向:

// Q1
var a = 1;
function print () {
 console.log(this.a)
}
​
print()
// Q2
const obj = {
 a: 2,
 print: function () { console.log(this.a) }
}
obj.print();
​
// Q3
const obj = {
 a: 3,
 print: function () { console.log(this.a) }
}
​~~~~
const foo = obj.print; 
foo()
​
// Q4
const obj = {
 a: 4,
 print: () => { console.log(this.a) }
}
obj.print();
​
// Q5
var a = 5
const obj = {
 a: 6,
 print: () => { console.log(this.a) }
}
obj.print.call({a: 7});
​
// Q6
function Person () {
 this.a = 8
 this.print = function () {console.log(this.a)}
 return {a: 9}
}
​
const p = new Person()
console.log(p.a)
console.log(p.print())
​
// Q7
'use strict';
var a = 1;
​
function print () {
 console.log(this.a)
}
print()**

判断数组的几种方法?为什么要用Object.prototype.toString(),

数组的.toString()不行吗,输出什么

讲一下事件流,怎么阻止冒泡?如何移除事件绑定?

flex常见属性,如何实现类似快手这种视频列表的瀑布流布局(一行两个) flex:1的含义

又讲一下你觉得自己写的最好的一个组件?

编程题:

  1. 什么是面向对象?面向对象特性?与面向过程的区别?
    把大象放进冰箱 分别用面向对象,面向过程,函数式编程实现。
  2. 求二叉树每层的最大节点,放入数组输出。(需要自己构造一棵二叉树去验证) 问优化:怎么只用一层循环实现层序遍历
  3. 实现复杂版本的bind,可new可继承
  // 可new可继承版本的bind
  Function.prototype.bind = function (context, ...outerArgs) {
      let that = this;
      function res (...innerArgs) {
        if (this instanceof res) {
          // new操作符执行时
          // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
          that.call(this, ...outerArgs, ...innerArgs)
        } else {
          // 普通bind
          that.call(context, ...outerArgs, ...innerArgs)
        }
      }
      res.prototype = this.prototype //!!!
      return res
    }

其他:问了实习项目,主要关心做的项目的作用和难点

一面总结:一面问的还是比较基础的,算法和手写都是常考的类型,但是面试官除了前端相关的知识之外上来就问到面向对象和面向过程的思想,以及函数式编程,说明面试官还是比较注重编程思想的。

二面 50min

跨域,需要解释具体实现过程

react和vue的区别

vue 路由,hash和history的区别,你们项目中是怎么进行状态管理的,怎么配置路由的?

常见的HTTP请求以及每个请求的作用?GET和POST的区别

ES6 ,ES7新特性?

前端安全措施,常见的安全问题,XSS?CSRF

https ca证书的作用,SSL层的作用

路由切换页面的原理??

vue和react的区别?

问输出:

// 1.
a = 100;
let a;
a = 10;
function test(num) {
console.log(a);
 a = num;
}
console.log(a);
test(5);
console.log(a);

// 2.
const a;
a = 10;(会报错)

编程题:

  1. 实现一个函数,将输入的数组转为链表,并实现一个方法向链表指定位置插入值
  2. 两个无序数组合并成一个有序数组,问时间复杂度。 延伸:快排和冒泡排序的时间复杂度,使用场景
  3. 实现toFix函数

二面虽然问的没有很难,但自己答得不是很好,跨域和安全也都答不上来,太久没复习全忘了,手写题答得还可以。本来以为希望不大,结果面完之后面试官立刻问我有没有时间,要继续三面。 结果三面的面试官没有时间,改到后天了,还能有机会再复习一下

三面 80min

你知道的数据结构有哪些?

数组是数据结构吗?不是,那为什么?什么是数据结构

你刚刚说到了堆,堆是一种新的数据结构吗?不是,是完全二叉树。什么是完全二叉树?

树和图的区别? 图的应用场景?

react生命周期,忘记了,说了vue的,各个生命周期的作用?

网络模型以及每层的协议?
这里写图片描述

CSS:

css动画的实现方式?具体一些
position每种属性的作用

如何尽可能发现代码中的bug,有没有一些工具去辅助

编程题:

  1. 说一下instanceof的原理,实现一下
  2. 知道map方法吗?实现一下。 reduce和基本版都写了
  3. 实现ajax,至少实现get和post方法。这里深挖了get和post具体的传参方式以及ajax中具体是如何实现的
  4. 实现一个模态框组件,用vue和react均可(要能传递确定取消事件函数,有遮罩,居中)

其他:

项目难点和收获

对哪种框架比较熟悉

别人的评价

自身的优点和缺点

压力最大的时候?怎么排解压力

你对于以后前端学习的规划?

三面结束后,过了20min HR小姐姐就给我打电话约了下午HR面

三面问了足足80分钟,面试官从数据结构,计算机网络,js原理,到原生API实现,组件实现,比较注重面试者对实现原理的思考(而不是单纯的只会用这个API或者这个组件)。另外也会考察考察面试者的综合素质和抗压情况。

ps: 感觉三面的面试官是个大佬,比较严肃有气势,我有的不确定的东西可能说话带着疑问句,他就直接说你不要问我是不是,直接说就就行了。 面试官比较直接,我答的出来的题他就直接说看来这个题你已经掌握了,我答的不好的他也当场说你这个题答得不好。比较庆幸自己面试的前一天晚上复习时押了几个题,押到了网络模型以及每层协议,手写ajax和map的实现,要不然这场面试估计凉凉,还是自己太菜了~~唉

另外有一个小插曲是之前字节同学有说面试官可能会问前面的面试答的怎么样,有哪些不会的,可能会再问有没有学习,所以我就把前面遇到的不会的也看了一下。 但三面面试官出题的时候说了一句让我看看前面还有哪些没出过的,要找没出过的题考我,所以这个还是分人分部门啦~

HR面 40min

之前遇到的hr面都是电话面,这次的比较正式,让下载飞书,通过飞书进入链接地址进行视频面试。

对前面的面试有什么感受?

你觉得三位面试官是怎么样的人?为什么?

如何准备面试的?之前有看过面经吗?

喜欢团队合作还是自己工作

合作遇到不好相处的人怎么办?

团队利益优先还是个人利益优先?

讲一讲最近在看什么书

户籍所在地?高考考了多少分?

兴趣爱好?

最黑暗的时刻?怎么排解

对考研成绩和学校满意吗?

对未来职业发展的规划?

分别用三个词形容自己,同事和朋友怎么形容你?

平时都是怎么学前端的?
(在这里我有说到喜欢通过看技术文档学习,HR小姐姐表示那字节还挺适合我的,因为内部有很多技术文档可供学习)

github有经常更新吗?

介绍了上下班时间,薪资待遇

问了能实习多久,并且表示如果在这边实习到毕业可以直接转正(本人2022届的研究生),也说如果考虑到毕设的话是可以请长假的,一个多月的那种长假。

总之,hr面就比较轻松了,和小姐姐聊的挺愉快。

总结

最后,结合这次面试和前面字节其他部门的面试来看,感觉字节还是很注重基础和底层原理的。

框架类的东西问的不太多,主要还是js要学好(会考手写call, bind, apply, ajax, promise, map方法),还有计算机网络的东西,尤其是与HTTP请求相关的知识也一定要掌握。

编程题: 算法不会问的太难,基本都是简单的难度,大多会结合数据结构来考,比如树和链表的操作,另外还要掌握至少三四排序算法以及其时间复杂度。 也会让手写一些简单的api,或者封装一个组件,算法和一些简单的API一般都是需要当场运行的,所以写的时候要细心,如果运行总是报错就很尴尬了。

常考手写可参考另一篇文章,覆盖了容易考到的各种高频手写~~ https://segmentfault.com/a/11...

项目方面考察的点:

* 项目内容,所用技术,自己在里面做了哪些工作
* 项目的难点和最大收获(谈难点时通常会问的特别深入,避免给自己挖坑)
* 项目中遇到的其他问题(可以是非技术上的),你会怎么解决?

hr面: 主要关注候选人的这几个点:

* 学习成绩,在校经历
* 团队合作能力,处理团队问题的方式
* 抗压能力
* 个人性格,爱好
* 能否长期实习,有无转正意向
* 对未来职业发展的规划
* 学习能力
查看原文

znhDT0p4 赞了文章 · 2020-12-19

字节跳动前端实习面经(三轮技术面+hr面)2020.12

个人背景

本人研二,科班,学前端时间四个月左右,有做过react小程序,但是对react的理解不是很深。有一段四个月的前端实习经历,用的是vue。投的字节跳动北京的base。

一面 60min

最近在学什么?node.js,为什么要学node.js,讲一下你学到node.js的哪些知识

常见数据类型?
ES6有哪些数据类型,set和map区别?set放NaN会有几个?NaN全等于NaN吗?答不全等,问那为什么set里面只能放一个

问输出,考察this指向:

// Q1
var a = 1;
function print () {
 console.log(this.a)
}
​
print()
// Q2
const obj = {
 a: 2,
 print: function () { console.log(this.a) }
}
obj.print();
​
// Q3
const obj = {
 a: 3,
 print: function () { console.log(this.a) }
}
​~~~~
const foo = obj.print; 
foo()
​
// Q4
const obj = {
 a: 4,
 print: () => { console.log(this.a) }
}
obj.print();
​
// Q5
var a = 5
const obj = {
 a: 6,
 print: () => { console.log(this.a) }
}
obj.print.call({a: 7});
​
// Q6
function Person () {
 this.a = 8
 this.print = function () {console.log(this.a)}
 return {a: 9}
}
​
const p = new Person()
console.log(p.a)
console.log(p.print())
​
// Q7
'use strict';
var a = 1;
​
function print () {
 console.log(this.a)
}
print()**

判断数组的几种方法?为什么要用Object.prototype.toString(),

数组的.toString()不行吗,输出什么

讲一下事件流,怎么阻止冒泡?如何移除事件绑定?

flex常见属性,如何实现类似快手这种视频列表的瀑布流布局(一行两个) flex:1的含义

又讲一下你觉得自己写的最好的一个组件?

编程题:

  1. 什么是面向对象?面向对象特性?与面向过程的区别?
    把大象放进冰箱 分别用面向对象,面向过程,函数式编程实现。
  2. 求二叉树每层的最大节点,放入数组输出。(需要自己构造一棵二叉树去验证) 问优化:怎么只用一层循环实现层序遍历
  3. 实现复杂版本的bind,可new可继承
  // 可new可继承版本的bind
  Function.prototype.bind = function (context, ...outerArgs) {
      let that = this;
      function res (...innerArgs) {
        if (this instanceof res) {
          // new操作符执行时
          // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
          that.call(this, ...outerArgs, ...innerArgs)
        } else {
          // 普通bind
          that.call(context, ...outerArgs, ...innerArgs)
        }
      }
      res.prototype = this.prototype //!!!
      return res
    }

其他:问了实习项目,主要关心做的项目的作用和难点

一面总结:一面问的还是比较基础的,算法和手写都是常考的类型,但是面试官除了前端相关的知识之外上来就问到面向对象和面向过程的思想,以及函数式编程,说明面试官还是比较注重编程思想的。

二面 50min

跨域,需要解释具体实现过程

react和vue的区别

vue 路由,hash和history的区别,你们项目中是怎么进行状态管理的,怎么配置路由的?

常见的HTTP请求以及每个请求的作用?GET和POST的区别

ES6 ,ES7新特性?

前端安全措施,常见的安全问题,XSS?CSRF

https ca证书的作用,SSL层的作用

路由切换页面的原理??

vue和react的区别?

问输出:

// 1.
a = 100;
let a;
a = 10;
function test(num) {
console.log(a);
 a = num;
}
console.log(a);
test(5);
console.log(a);

// 2.
const a;
a = 10;(会报错)

编程题:

  1. 实现一个函数,将输入的数组转为链表,并实现一个方法向链表指定位置插入值
  2. 两个无序数组合并成一个有序数组,问时间复杂度。 延伸:快排和冒泡排序的时间复杂度,使用场景
  3. 实现toFix函数

二面虽然问的没有很难,但自己答得不是很好,跨域和安全也都答不上来,太久没复习全忘了,手写题答得还可以。本来以为希望不大,结果面完之后面试官立刻问我有没有时间,要继续三面。 结果三面的面试官没有时间,改到后天了,还能有机会再复习一下

三面 80min

你知道的数据结构有哪些?

数组是数据结构吗?不是,那为什么?什么是数据结构

你刚刚说到了堆,堆是一种新的数据结构吗?不是,是完全二叉树。什么是完全二叉树?

树和图的区别? 图的应用场景?

react生命周期,忘记了,说了vue的,各个生命周期的作用?

网络模型以及每层的协议?
这里写图片描述

CSS:

css动画的实现方式?具体一些
position每种属性的作用

如何尽可能发现代码中的bug,有没有一些工具去辅助

编程题:

  1. 说一下instanceof的原理,实现一下
  2. 知道map方法吗?实现一下。 reduce和基本版都写了
  3. 实现ajax,至少实现get和post方法。这里深挖了get和post具体的传参方式以及ajax中具体是如何实现的
  4. 实现一个模态框组件,用vue和react均可(要能传递确定取消事件函数,有遮罩,居中)

其他:

项目难点和收获

对哪种框架比较熟悉

别人的评价

自身的优点和缺点

压力最大的时候?怎么排解压力

你对于以后前端学习的规划?

三面结束后,过了20min HR小姐姐就给我打电话约了下午HR面

三面问了足足80分钟,面试官从数据结构,计算机网络,js原理,到原生API实现,组件实现,比较注重面试者对实现原理的思考(而不是单纯的只会用这个API或者这个组件)。另外也会考察考察面试者的综合素质和抗压情况。

ps: 感觉三面的面试官是个大佬,比较严肃有气势,我有的不确定的东西可能说话带着疑问句,他就直接说你不要问我是不是,直接说就就行了。 面试官比较直接,我答的出来的题他就直接说看来这个题你已经掌握了,我答的不好的他也当场说你这个题答得不好。比较庆幸自己面试的前一天晚上复习时押了几个题,押到了网络模型以及每层协议,手写ajax和map的实现,要不然这场面试估计凉凉,还是自己太菜了~~唉

另外有一个小插曲是之前字节同学有说面试官可能会问前面的面试答的怎么样,有哪些不会的,可能会再问有没有学习,所以我就把前面遇到的不会的也看了一下。 但三面面试官出题的时候说了一句让我看看前面还有哪些没出过的,要找没出过的题考我,所以这个还是分人分部门啦~

HR面 40min

之前遇到的hr面都是电话面,这次的比较正式,让下载飞书,通过飞书进入链接地址进行视频面试。

对前面的面试有什么感受?

你觉得三位面试官是怎么样的人?为什么?

如何准备面试的?之前有看过面经吗?

喜欢团队合作还是自己工作

合作遇到不好相处的人怎么办?

团队利益优先还是个人利益优先?

讲一讲最近在看什么书

户籍所在地?高考考了多少分?

兴趣爱好?

最黑暗的时刻?怎么排解

对考研成绩和学校满意吗?

对未来职业发展的规划?

分别用三个词形容自己,同事和朋友怎么形容你?

平时都是怎么学前端的?
(在这里我有说到喜欢通过看技术文档学习,HR小姐姐表示那字节还挺适合我的,因为内部有很多技术文档可供学习)

github有经常更新吗?

介绍了上下班时间,薪资待遇

问了能实习多久,并且表示如果在这边实习到毕业可以直接转正(本人2022届的研究生),也说如果考虑到毕设的话是可以请长假的,一个多月的那种长假。

总之,hr面就比较轻松了,和小姐姐聊的挺愉快。

总结

最后,结合这次面试和前面字节其他部门的面试来看,感觉字节还是很注重基础和底层原理的。

框架类的东西问的不太多,主要还是js要学好(会考手写call, bind, apply, ajax, promise, map方法),还有计算机网络的东西,尤其是与HTTP请求相关的知识也一定要掌握。

编程题: 算法不会问的太难,基本都是简单的难度,大多会结合数据结构来考,比如树和链表的操作,另外还要掌握至少三四排序算法以及其时间复杂度。 也会让手写一些简单的api,或者封装一个组件,算法和一些简单的API一般都是需要当场运行的,所以写的时候要细心,如果运行总是报错就很尴尬了。

常考手写可参考另一篇文章,覆盖了容易考到的各种高频手写~~ https://segmentfault.com/a/11...

项目方面考察的点:

* 项目内容,所用技术,自己在里面做了哪些工作
* 项目的难点和最大收获(谈难点时通常会问的特别深入,避免给自己挖坑)
* 项目中遇到的其他问题(可以是非技术上的),你会怎么解决?

hr面: 主要关注候选人的这几个点:

* 学习成绩,在校经历
* 团队合作能力,处理团队问题的方式
* 抗压能力
* 个人性格,爱好
* 能否长期实习,有无转正意向
* 对未来职业发展的规划
* 学习能力
查看原文

赞 13 收藏 8 评论 0

znhDT0p4 关注了用户 · 2020-12-19

sprina @sprina1997

力求用最简单的思想去理解清楚复杂概念

关注 15

znhDT0p4 关注了标签 · 2020-12-19

javascript

JavaScript 是一门弱类型的动态脚本语言,支持多种编程范式,包括面向对象和函数式编程,被广泛用于 Web 开发。

一般来说,完整的JavaScript包括以下几个部分:

  • ECMAScript,描述了该语言的语法和基本对象
  • 文档对象模型(DOM),描述处理网页内容的方法和接口
  • 浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口

它的基本特点如下:

  • 是一种解释性脚本语言(代码不进行预编译)。
  • 主要用来向HTML页面添加交互行为。
  • 可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。

JavaScript常用来完成以下任务:

  • 嵌入动态文本于HTML页面
  • 对浏览器事件作出响应
  • 读写HTML元素
  • 在数据被提交到服务器之前验证数据
  • 检测访客的浏览器信息

《 Javascript 优点在整个语言中占多大比例?

关注 165913

znhDT0p4 关注了标签 · 2020-12-19

前端

Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征。在互联网的演化进程中,网页制作是Web 1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行为也以浏览为主。2005年以后,互联网进入Web 2.0时代,各种类似桌面软件的Web应用大量涌现,网站的前端由此发生了翻天覆地的变化。网页不再只是承载单一的文字和图片,各种富媒体让网页的内容更加生动,网页上软件化的交互形式为用户提供了更好的使用体验,这些都是基于前端技术实现的。

Web前端优化
  1. 尽量减少HTTP请求 (Make Fewer HTTP Requests)
  2. 减少 DNS 查找 (Reduce DNS Lookups)
  3. 避免重定向 (Avoid Redirects)
  4. 使得 Ajax 可缓存 (Make Ajax Cacheable)
  5. 延迟载入组件 (Post-load Components)
  6. 预载入组件 (Preload Components)
  7. 减少 DOM 元素数量 (Reduce the Number of DOM Elements)
  8. 切分组件到多个域 (Split Components Across Domains)
  9. 最小化 iframe 的数量 (Minimize the Number of iframes)
  10. 杜绝 http 404 错误 (No 404s)

关注 183916

znhDT0p4 关注了标签 · 2020-12-19

程序员

一种近几十年来出现的新物种,是工业革命的产物。英文(Programmer Monkey)是一种非常特殊的、可以从事程序开发、维护的动物。一般分为程序设计猿和程序编码猿,但两者的界限并不非常清楚,都可以进行开发、维护工作,特别是在中国,而且最重要的一点,二者都是一种非常悲剧的存在。

国外的程序员节

国外的程序员节,(英语:Programmer Day,俄语:День программи́ста)是一个俄罗斯官方节日,日期是每年的第 256(0x100) 天,也就是平年的 9 月 13 日和闰年的 9 月 12 日,选择 256 是因为它是 2 的 8 次方,比 365 少的 2 的最大幂。

1024程序员节,中国程序员节

1024是2的十次方,二进制计数的基本计量单位之一。程序员(英文Programmer)是从事程序开发、维护的专业人员。程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。1GB=1024M,而1GB与1级谐音,也有一级棒的意思。

从2012年,SegmentFault 创办开始我们就从网络上引导社区的开发者,发展成中国程序员的节日 :) 计划以后每年10月24日定义为程序员节。以一个节日的形式,向通过Coding 改变世界,也以实际行动在浮躁的世界里,固执地坚持自己对于知识、技术和创新追求的程序员们表示致敬。并于之后的最为临近的周末为程序员们举行了一个盛大的狂欢派对。

2015的10月24日,我们SegmentFault 也在5个城市同时举办黑客马拉松这个特殊的形式,聚集开发者开一个编程大爬梯。

特别推荐:

【SF 黑客马拉松】:http://segmentfault.com/hacka...
【1024程序员闯关秀】小游戏,欢迎来挑战 http://segmentfault.com/game/

  • SF 开发者交流群:206236214
  • 黑客马拉松交流群:280915731
  • 开源硬件交流群:372308136
  • Android 开发者交流群:207895295
  • iOS 开发者交流群:372279630
  • 前端开发者群:174851511

欢迎开发者加入~

交流群信息


程序员相关问题集锦:

  1. 《程序员如何选择自己的第二语言》
  2. 《如何成为一名专业的程序员?》
  3. 《如何用各种编程语言书写hello world》
  4. 《程序员们最常说的谎话是什么?》
  5. 《怎么加入一个开源项目?》
  6. 《是要精于单挑,还是要善于合作?》
  7. 《来秀一下你屎一般的代码...》
  8. 《如何区分 IT 青年的“普通/文艺/二逼”属性?》
  9. 程序员必读书籍有哪些?
  10. 你经常访问的技术社区或者技术博客(IT类)有哪些?
  11. 如何一行代码弄崩你的程序?我先来一发
  12. 编程基础指的是什么?
  13. 后端零起步:学哪一种比较好?
  14. 大家都用什么键盘写代码的?

爱因斯坦

程序猿崛起

关注 143706

znhDT0p4 关注了标签 · 2020-12-19

vue.js

Reactive Components for Modern Web Interfaces.

Vue.js 是一个用于创建 web 交互界面的。其特点是

  • 简洁 HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。
  • 数据驱动 自动追踪依赖的模板表达式和计算属性。
  • 组件化 用解耦、可复用的组件来构造界面。
  • 轻量 ~24kb min+gzip,无依赖。
  • 快速 精确有效的异步批量 DOM 更新。
  • 模块友好 通过 NPM 或 Bower 安装,无缝融入你的工作流。

官网:https://vuejs.org
GitHub:https://github.com/vuejs/vue

关注 127371

znhDT0p4 关注了标签 · 2020-12-19

docker

an open source project to pack, ship and run any application as a lightweight container ! By Lock !

关注 34763

znhDT0p4 关注了标签 · 2020-12-19

css

层叠样式表(英语:Cascading Style Sheets,简写CSS),又称串样式列表,由W3C定义和维护的标准,一种用来为结构化文档(如HTML文档或XML应用)添加样式(字体、间距和颜色等)的计算机语言。

关注 87411

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-12-19
个人主页被 60 人浏览