乐趣区

关于前端:京东前端一面面试题

对 JSON 的了解

JSON 是一种基于文本的轻量级的数据交换格局。它能够被任何的编程语言读取和作为数据格式来传递。

在我的项目开发中,应用 JSON 作为前后端数据交换的形式。在前端通过将一个合乎 JSON 格局的数据结构序列化为
JSON 字符串,而后将它传递到后端,后端通过 JSON 格局的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为 JSON 的语法是基于 js 的,因而很容易将 JSON 和 js 中的对象弄混,然而应该留神的是 JSON 和 js 中的对象不是一回事,JSON 中对象格局更加严格,比如说在 JSON 中属性值不能为函数,不能呈现 NaN 这样的属性值等,因而大多数的 js 对象是不合乎 JSON 对象的格局的。

在 js 中提供了两个函数来实现 js 数据结构和 JSON 格局的转换解决,

  • JSON.stringify 函数,通过传入一个合乎 JSON 格局的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不合乎 JSON 格局,那么在序列化的时候会对这些值进行对应的非凡解决,使其符合规范。在前端向后端发送数据时,能够调用这个函数将数据对象转化为 JSON 格局的字符串。
  • JSON.parse() 函数,这个函数用来将 JSON 格局的字符串转换为一个 js 数据结构,如果传入的字符串不是规范的 JSON 格局的字符串的话,将会抛出谬误。当从后端接管到 JSON 格局的字符串时,能够通过这个办法来将其解析为一个 js 数据结构,以此来进行数据的拜访。

JavaScript 中如何进行隐式类型转换?

首先要介绍 ToPrimitive 办法,这是 JavaScript 中每个值隐含的自带的办法,用来将值(无论是根本类型值还是对象)转换为根本类型值。如果值为根本类型,则间接返回值自身;如果值为对象,其看起来大略是这样:

/*** @obj 须要转换的对象 * @type 冀望的后果类型 */
ToPrimitive(obj,type)

type的值为 number 或者string

(1)当 typenumber时规定如下:

  • 调用 objvalueOf办法,如果为原始值,则返回,否则下一步;
  • 调用 objtoString办法,后续同上;
  • 抛出TypeError 异样。

(2)当 typestring时规定如下:

  • 调用 objtoString办法,如果为原始值,则返回,否则下一步;
  • 调用 objvalueOf办法,后续同上;
  • 抛出TypeError 异样。

能够看出两者的次要区别在于调用 toStringvalueOf的先后顺序。默认状况下:

  • 如果对象为 Date 对象,则 type 默认为string
  • 其余状况下,type默认为number

总结下面的规定,对于 Date 以外的对象,转换为根本类型的大略规定能够概括为一个函数:

var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN

而 JavaScript 中的隐式类型转换次要产生在 +、-、*、/ 以及 ==、>、< 这些运算符之间。而这些运算符只能操作根本类型值,所以在进行这些运算前的第一步就是将两边的值用 ToPrimitive 转换成根本类型,再进行操作。

以下是根本类型的值在不同操作符的状况下隐式转换的规定(对于对象,其会被 ToPrimitive 转换成根本类型,所以最终还是要利用根本类型转换规定):

  1. +操作符 + 操作符的两边有至多一个 string 类型变量时,两边的变量都会被隐式转换为字符串;其余状况下两边的变量都会被转换为数字。
1 + '23' // '123'
 1 + false // 1 
 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
 '1' + false // '1false'
 false + true // 1
  1. -*\操作符

NaN也是一个数字

1 * '23' // 23
 1 * false // 0
 1 / 'aa' // NaN
  1. 对于 == 操作符

操作符两边的值都尽量转成number

3 == true // false, 3 转为 number 为 3,true 转为 number 为 1
'0' == false //true, '0' 转为 number 为 0,false 转为 number 为 0
'0' == 0 // '0' 转为 number 为 0
  1. 对于 <>比拟符

如果两边都是字符串,则比拟字母表程序:

'ca' < 'bd' // false
'a' < 'b' // true

其余状况下,转换为数字再比拟:

'12' < 13 // true
false > -1 // true

以上说的是根本类型的隐式转换,而对象会被 ToPrimitive 转换为根本类型再进行转换:

var a = {}
a > 2 // false

其比照过程如下:

a.valueOf() // {}, 下面提到过,ToPrimitive 默认 type 为 number,所以先 valueOf,后果还是个对象,下一步
a.toString() // "[object Object]",当初是一个字符串了
Number(a.toString()) // NaN,依据下面 < 和 > 操作符的规定,要转换成数字
NaN > 2 //false,得出比拟后果

又比方:

var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"

运算过程如下:

a.valueOf() // {},下面提到过,ToPrimitive 默认 type 为 number,所以先 valueOf,后果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"

宏工作和微工作别离有哪些

  • 微工作包含:promise 的回调、node 中的 process.nextTick、对 Dom 变动监听的 MutationObserver。
  • 宏工作包含:script 脚本的执行、setTimeout,setInterval,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。

let、const、var 的区别

(1)块级作用域: 块作用域由 {}包含,let 和 const 具备块级作用域,var 不存在块级作用域。块级作用域解决了 ES5 中的两个问题:

  • 内层变量可能笼罩外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量晋升: var 存在变量晋升,let 和 const 不存在变量晋升,即在变量只能在申明之后应用,否在会报错。

(3)给全局增加属性: 浏览器的全局对象是 window,Node 的全局对象是 global。var 申明的变量为全局变量,并且会将该变量增加为全局对象的属性,然而 let 和 const 不会。

(4)反复申明: var 申明变量时,能够反复申明变量,后申明的同名变量会笼罩之前申明的遍历。const 和 let 不容许反复申明变量。

(5)暂时性死区: 在应用 let、const 命令申明变量之前,该变量都是不可用的。这在语法上,称为 暂时性死区。应用 var 申明的变量不存在暂时性死区。

(6)初始值设置: 在变量申明时,var 和 let 能够不必设置初始值。而 const 申明变量必须设置初始值。

(7)指针指向: let 和 const 都是 ES6 新增的用于创立变量的语法。let 创立的变量是能够更改指针指向(能够从新赋值)。但 const 申明的变量是不容许扭转指针的指向。

区别 var let const
是否有块级作用域 × ✔️ ✔️
是否存在变量晋升 ✔️ × ×
是否增加全局属性 ✔️ × ×
是否反复申明变量 ✔️ × ×
是否存在暂时性死区 × ✔️ ✔️
是否必须设置初始值 × × ✔️
是否扭转指针指向 ✔️ ✔️ ×

Promise.allSettled

形容 :等到所有promise 都返回后果,就返回一个 promise 实例。

实现

Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = {
                            status: 'fulfilled',
                            value: value
                        };
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => {
                        count++;
                        result[index] = {
                            status: 'rejected'.
                            reason: reason
                        };
                        if(count === promises.length) resolve(result);
                    }
                );
            });
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

手写题:数组去重

Array.from(new Set([1, 1, 2, 2]))

代码输入后果

setTimeout(function () {console.log(1);
}, 100);

new Promise(function (resolve) {console.log(2);
  resolve();
  console.log(3);
}).then(function () {console.log(4);
  new Promise((resove, reject) => {console.log(5);
    setTimeout(() =>  {console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);

输入后果为:

2
3
7
8
4
5
6
1

代码执行过程如下:

  1. 首先遇到定时器,将其退出到宏工作队列;
  2. 遇到 Promise,首先执行外面的同步代码,打印出 2,遇到 resolve,将其退出到微工作队列,执行前面同步代码,打印出 3;
  3. 继续执行 script 中的代码,打印出 7 和 8,至此第一轮代码执行实现;
  4. 执行微工作队列中的代码,首先打印出 4,如遇到 Promise,执行其中的同步代码,打印出 5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
  5. 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为 100ms,第二个定时器的工夫为 10ms,所以先执行第二个定时器,打印出 6;
  6. 此时微工作队列为空,继续执行宏工作队列,打印出 1。

做完这道题目,咱们就须要分外留神,每个定时器的工夫,并不是所有定时器的工夫都为 0 哦。

函数防抖

触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。

简略版:函数外部反对应用 this 和 event 对象;

function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function(){func.apply(context, args)
        }, wait);
    }
}

应用:

var node = document.getElementById('layout')
function getUserAction(e) {console.log(this, e)  // 别离打印:node 这个节点 和 MouseEvent
    node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)

最终版:除了反对 this 和 event 外,还反对以下性能:

  • 反对立刻执行;
  • 函数可能有返回值;
  • 反对勾销性能;
function debounce(func, wait, immediate) {
    var timeout, result;

    var debounced = function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果曾经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){timeout = null;}, wait)
            if (callNow) result = func.apply(context, args)
        } else {timeout = setTimeout(function(){func.apply(context, args)
            }, wait);
        }
        return result;
    };

    debounced.cancel = function() {clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
}

应用:

var setUseAction = debounce(getUserAction, 10000, true);
// 应用防抖
node.onmousemove = setUseAction

// 勾销防抖
setUseAction.cancel()

Cookie 有哪些字段,作用别离是什么

Cookie 由以下字段组成:

  • Name:cookie 的名称
  • Value:cookie 的值,对于认证 cookie,value 值包含 web 服务器所提供的拜访令牌;
  • Size:cookie 的大小
  • Path:能够拜访此 cookie 的页面门路。比方 domain 是 abc.com,path 是 /test,那么只有/test 门路下的页面能够读取此 cookie。
  • Secure:指定是否应用 HTTPS 平安协定发送 Cookie。应用 HTTPS 平安协定,能够爱护 Cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。该办法也可用于 Web 站点的身份甄别,即在 HTTPS 的连贯建设阶段,浏览器会查看 Web 网站的 SSL 证书的有效性。然而基于兼容性的起因(比方有些网站应用自签订的证书)在检测到 SSL 证书有效时,浏览器并不会立刻终止用户的连贯申请,而是显示平安危险信息,用户仍能够抉择持续拜访该站点。
  • Domain:能够拜访该 cookie 的域名,Cookie 机制并未遵循严格的同源策略,容许一个子域能够设置或获取其父域的 Cookie。当须要实现单点登录计划时,Cookie 的上述个性十分有用,然而也减少了 Cookie 受攻打的危险,比方攻击者能够借此动员会话定置攻打。因此,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻打产生的范畴。
  • HTTP:该字段蕴含 HTTPOnly 属性,该属性用来设置 cookie 是否通过脚本来拜访,默认为空,即能够通过脚本拜访。在客户端是不能通过 js 代码去设置一个 httpOnly 类型的 cookie 的,这种类型的 cookie 只能通过服务端来设置。该属性用于避免客户端脚本通过document.cookie 属性拜访 Cookie,有助于爱护 Cookie 不被跨站脚本攻打窃取或篡改。然而,HTTPOnly 的利用仍存在局限性,一些浏览器能够阻止客户端脚本对 Cookie 的读操作,但容许写操作;此外大多数浏览器仍容许通过 XMLHTTP 对象读取 HTTP 响应中的 Set-Cookie 头。
  • Expires/Max-size:此 cookie 的超时工夫。若设置其值为一个工夫,那么当达到此工夫后,此 cookie 生效。不设置的话默认值是 Session,意思是 cookie 会和 session 一起生效。当浏览器敞开(不是浏览器标签页,而是整个浏览器) 后,此 cookie 生效。

总结: 服务器端能够应用 Set-Cookie 的响应头部来配置 cookie 信息。一条 cookie 包含了 5 个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 生效的工夫,domain 是域名、path 是门路,domain 和 path 一起限度了 cookie 可能被哪些 url 拜访。secure 规定了 cookie 只能在确保安全的状况下传输,HttpOnly 规定了这个 cookie 只能被服务器拜访,不能应用 js 脚本拜访。

代码输入后果

async function async1() {console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {console.log('timer1')
  }, 0)
}
async function async2() {setTimeout(() => {console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {console.log('timer3')
}, 0)
console.log("start")

输入后果如下:

async1 start
async2
start
async1 end
timer2
timer3
timer1

代码的执行过程如下:

  1. 首先进入async1,打印出async1 start
  2. 之后遇到async2,进入async2,遇到定时器timer2,退出宏工作队列,之后打印async2
  3. 因为 async2 阻塞了前面代码的执行,所以执行前面的定时器timer3,将其退出宏工作队列,之后打印start
  4. 而后执行 async2 前面的代码,打印出async1 end,遇到定时器 timer1,将其退出宏工作队列;
  5. 最初,宏工作队列有三个工作,先后顺序为timer2timer3timer1,没有微工作,所以间接所有的宏工作依照先进先出的准则执行。

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

Promise 的根本用法

(1)创立 Promise 对象

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已胜利)和 rejected(已失败)。

Promise 构造函数承受一个函数作为参数,该函数的两个参数别离是 resolvereject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作胜利 */){resolve(value);
  } else {reject(error);
  }
});

个别状况下都会应用 new Promise() 来创立 promise 对象,然而也能够应用 promise.resolvepromise.reject这两个办法:

  • Promise.resolve

Promise.resolve(value)的返回值也是一个 promise 对象,能够对返回值进行.then 调用,代码如下:

Promise.resolve(11).then(function(value){console.log(value); // 打印出 11
});

resolve(11)代码中,会让 promise 对象进入确定 (resolve 状态),并将参数 11 传递给前面的 then 所指定的onFulfilled 函数;

创立 promise 对象能够应用 new Promise 的模式创建对象,也能够应用 Promise.resolve(value) 的模式创立 promise 对象;

  • Promise.reject

Promise.reject 也是 new Promise 的快捷模式,也创立一个 promise 对象。代码如下:

Promise.reject(new Error(“我错了,请原谅俺!!”));

就是上面的代码 new Promise 的简略模式:

new Promise(function(resolve,reject){reject(new Error("我错了!"));
});

上面是应用 resolve 办法和 reject 办法:

function testPromise(ready) {return new Promise(function(resolve,reject){if(ready) {resolve("hello world");
    }else {reject("No thanks");
    }
  });
};
// 办法调用
testPromise(true).then(function(msg){console.log(msg);
},function(error){console.log(error);
});

下面的代码的含意是给 testPromise 办法传递一个参数,返回一个 promise 对象,如果为 true 的话,那么调用 promise 对象中的 resolve() 办法,并且把其中的参数传递给前面的 then 第一个函数内,因而打印出“hello world”, 如果为 false 的话,会调用 promise 对象中的 reject() 办法,则会进入 then 的第二个函数内,会打印No thanks

(2)Promise 办法

Promise 有五个罕用的办法:then()、catch()、all()、race()、finally。上面就来看一下这些办法。

  1. then()

当 Promise 执行的内容合乎胜利条件时,调用 resolve 函数,失败就调用 reject 函数。Promise 创立完了,那该如何调用呢?

promise.then(function(value) {// success}, function(error) {// failure});

then办法能够承受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中第二个参数能够省略。then办法返回的是一个新的 Promise 实例(不是原来那个 Promise 实例)。因而能够采纳链式写法,即 then 办法前面再调用另一个 then 办法。

当要写有程序的异步事件时,须要串行时,能够这样写:

let promise = new Promise((resolve,reject)=>{ajax('first').success(function(res){resolve(res);
    })
})
promise.then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{})

那当要写的事件没有程序或者关系时,还如何写呢?能够应用all 办法来解决。

2. catch()

Promise 对象除了有 then 办法,还有一个 catch 办法,该办法相当于 then 办法的第二个参数,指向 reject 的回调函数。不过 catch 办法还有一个作用,就是在执行 resolve 回调函数时,如果呈现谬误,抛出异样,不会进行运行,而是进入 catch 办法中。

p.then((data) => {console.log('resolved',data);
},(err) => {console.log('rejected',err);
     }
); 
p.then((data) => {console.log('resolved',data);
}).catch((err) => {console.log('rejected',err);
});

3. all()

all办法能够实现并行任务,它接管一个数组,数组的每一项都是一个 promise 对象。当数组中所有的 promise 的状态都达到 resolved 的时候,all办法的状态就会变成 resolved,如果有一个状态变成了rejected,那么all 办法的状态就会变成rejected

javascript
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果为:[1,2,3] 
})

调用 all 办法时的后果胜利的时候是回调函数的参数也是一个数组,这个数组按程序保留着每一个 promise 对象 resolve 执行时的值。

(4)race()

race办法和 all 一样,承受的参数是一个每项都是 promise 的数组,然而与 all 不同的是,当最先执行完的事件执行完之后,就间接返回该 promise 对象的值。如果第一个 promise 对象状态变成 resolved,那本身的状态变成了resolved;反之第一个promise 变成rejected,那本身状态就会变成rejected

let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{reject(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果:2
},rej=>{console.log(rej)};
)

那么 race 办法有什么理论作用呢?当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally办法用于指定不论 Promise 对象最初状态如何,都会执行的操作。该办法是 ES2018 引入规范的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

下面代码中,不论 promise 最初的状态,在执行完 thencatch指定的回调函数当前,都会执行 finally 办法指定的回调函数。

上面是一个例子,服务器应用 Promise 解决申请,而后应用 finally 办法关掉服务器。

server.listen(port)
  .then(function () {// ...})
  .finally(server.stop);

finally办法的回调函数不承受任何参数,这意味着没有方法晓得,后面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 办法外面的操作,应该是与状态无关的,不依赖于 Promise 的执行后果。finally实质上是 then 办法的特例:

promise
.finally(() => {// 语句});
// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

下面代码中,如果不应用 finally 办法,同样的语句须要为胜利和失败两种状况各写一次。有了 finally 办法,则只须要写一次。

两栏布局的实现

个别两栏布局指的是 右边一栏宽度固定,左边一栏宽度自适应,两栏布局的具体实现:

  • 利用浮动,将右边元素宽度设置为 200px,并且设置向左浮动。将左边元素的 margin-left 设置为 200px,宽度设置为 auto(默认为 auto,撑满整个父元素)。
.outer {height: 100px;}
.left {
  float: left;
  width: 200px;
  background: tomato;
}
.right {
  margin-left: 200px;
  width: auto;
  background: gold;
}
  • 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置 overflow: hidden; 这样左边就触发了 BFC,BFC 的区域不会与浮动元素产生重叠,所以两侧就不会产生重叠。
.left{
     width: 100px;
     height: 200px;
     background: red;
     float: left;
 }
 .right{
     height: 300px;
     background: blue;
     overflow: hidden;
 }
  • 利用 flex 布局,将右边元素设置为固定宽度 200px,将左边的元素设置为 flex:1。
.outer {
  display: flex;
  height: 100px;
}
.left {
  width: 200px;
  background: tomato;
}
.right {
  flex: 1;
  background: gold;
}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素设置为 absolute 定位,并且宽度设置为 200px。将左边元素的 margin-left 的值设置为 200px。
.outer {
  position: relative;
  height: 100px;
}
.left {
  position: absolute;
  width: 200px;
  height: 100px;
  background: tomato;
}
.right {
  margin-left: 200px;
  background: gold;
}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素宽度设置为 200px,左边元素设置为相对定位,右边定位为 200px,其余方向定位为 0。
.outer {
  position: relative;
  height: 100px;
}
.left {
  width: 200px;
  background: tomato;
}
.right {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 200px;
  background: gold;
}

深 / 浅拷贝

首先判断数据类型是否为对象,如果是对象(数组 | 对象),则递归(深 / 浅拷贝),否则间接拷贝。

function isObject(obj) {return typeof obj === "object" && obj !== null;}

这个函数只能判断 obj 是否是对象,无奈判断其具体是数组还是对象。

如果 new 一个箭头函数的会怎么样

箭头函数是 ES6 中的提出来的,它没有 prototype,也没有本人的 this 指向,更不能够应用 arguments 参数,所以不能 New 一个箭头函数。

new 操作符的实现步骤如下:

  1. 创立一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的 prototype 属性)
  3. 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象增加属性和办法)
  4. 返回新的对象

所以,下面的第二、三步,箭头函数都是没有方法执行的。

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题?你能说说如下代码的实现原理么?

1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题

  1. Vue 应用了 Object.defineProperty 实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的(这也就造成了 Vue 无奈检测到对象属性的增加或删除)

所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决

defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法

说一下类组件和函数组件的区别?

1. 语法上的区别:函数式组件是一个纯函数,它是须要承受 props 参数并且返回一个 React 元素就能够了。类组件是须要继承 React.Component 的,而且 class 组件须要创立 render 并且返回 React 元素,语法上来讲更简单。2. 调用形式

函数式组件能够间接调用,返回一个新的 React 元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的 render 办法来返回一个 React 元素。3. 状态治理

函数式组件没有状态治理,类组件有状态治理。4. 应用场景

类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。
退出移动版