当前环境中存在指向父级作用域的引用
描述:等到所有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"));
});
}
//通过call调用数组的slice方法来实现转换
Array.prototype.slice.call(arrayLike)
//通过call调用数组的splice方法来实现转换
Array.prototype.splice.call(arrayLike,0)
//通过apply调用数组的concat方法来实现转换
Array.prototype.concat.apply([],arrayLike)
//通过Array.from方法来实现转换
Array.from(arrayLike)
typeof 其中数组、对象、null都会被判断为Object,其他判断都正确
instanceof 只能判断引用数据类型,不能判断基本数据类型
constructor 它有2个作用 一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意的事情是如果创建一个对象来改变它的原型,constructor就不能来判断数据类型了
Object.prototype.toString.call()
Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
1、beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
2、created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 `$el` 属性。
3、beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
4、mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
5、beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
6、updated(更新后):在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
7、beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,`this` 仍能获取到实例。
8、destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 `keep-alive` 独有的生命周期,分别为 `activated` 和 `deactivated` 。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `activated` 钩子函数。
(1)call 函数的实现步骤:
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
(2)apply 函数的实现步骤:
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
(3)bind 函数的实现步骤:
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
核心思想:子类的原型成为父类的实例
实现:
function SuperType() {
this.colors = ['red', 'green'];
}
function SubType() {}
// 原型继承关键: 子类的原型成为父类的实例
SubType.prototype = new SuperType();
// 测试
let instance1 = new SubType();
instance1.colors.push('blue');
let instance2 = new SubType();
console.log(instance2.colors); // ['red', 'green', 'blue']
原型继承存在的问题:
核心思想:在子类构造函数中调用父类构造函数
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
this.getName = function() {
return this.name;
}
}
function SubType(name) {
// 继承 SuperType 并传参
SuperType.call(this, name);
}
// 测试
let instance1 = new SubType('instance1');
instance1.colors.push('blue');
console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2');
console.log(instance2.colors); // ['red', 'green']
构造函数继承的出现是为了解决了原型继承的引用值共享问题。优点是可以在子类构造函数中向父类构造函数传参。它存在的问题是:1)由于方法必须在构造函数中定义,因此方法不能重用。2)子类也不能访问父类原型上定义的方法。
核心思想:综合了原型链和构造函数,即,使用原型链继承原型上的方法,而通过构造函数继承实例属性。
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
// 实例属性
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
// 测试
let instance1 = new SubType('instance1', 1);
instance1.sayName(); // "instance1"
instance1.colors.push("blue");
console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2', 2);
instance2.sayName(); // "instance2"
console.log(instance2.colors); // ['red','green']
组合继承存在的问题是:父类构造函数始终会被调用两次:一次是在创建子类原型时new SuperType()
调用,另一次是在子类构造函数中SuperType.call()
调用。
核心思想:通过构造函数继承属性,但使用混合式原型继承方法,即,不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = Object.create(SuperType.prototype);
// 重写原型导致默认 constructor 丢失,手动将 constructor 指回 SubType
SubType.prototype.constructor = SubType;
核心思想:通过 extends
来实现类的继承(相当于 ES5
的原型继承)。通过 super
调用父类的构造方法 (相当于 ES5
的构造函数继承)。
实现:
class SuperType {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class SubType extends SuperType {
constructor(name, age) {
super(name); // 继承属性
this.age = age;
}
}
// 测试
let instance = new SubType('instance', 0);
instance.sayName(); // "instance"
虽然类继承使用的是新语法,但背后依旧使用的是原型链。
什么是keep-alive
我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);
当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
为什么要使用keep-alive
keep-alive技术的创建目的,能在多次HTTP之前重用同一个TCP连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等),参考如下示意图
客户端如何开启
在HTTP/1.0协议中,默认是关闭的,需要在http头加入"Connection: Keep-Alive”,才能启用Keep-Alive;
Connection: keep-alive
http 1.1中默认启用Keep-Alive,如果加入"Connection: close “,才关闭。
Connection: close
目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看服务器设置情况。
编程思想
一般面向对象包含:继承,封装,多态,抽象
1. 对象形式的继承
浅拷贝
var Person = {
name: 'poetry',
age: 18,
address: {
home: 'home',
office: 'office',
}
sclools: ['x','z'],
};
var programer = {
language: 'js',
};
function extend(p, c){
var c = c || {};
for( var prop in p){
c[prop] = p[prop];
}
}
extend(Person, programer);
programer.name; // poetry
programer.address.home; // home
programer.address.home = 'house'; //house
Person.address.home; // house
从上面的结果看出,浅拷贝的缺陷在于修改了子对象中引用类型的值,会影响到父对象中的值,因为在浅拷贝中对引用类型的拷贝只是拷贝了地址,指向了内存中同一个副本
深拷贝
function extendDeeply(p, c){
var c = c || {};
for (var prop in p){
if(typeof p[prop] === "object"){
c[prop] = (p[prop].constructor === Array)?[]:{};
extendDeeply(p[prop], c[prop]);
}else{
c[prop] = p[prop];
}
}
}
利用递归进行深拷贝,这样子对象的修改就不会影响到父对象
extendDeeply(Person, programer);
programer.address.home = 'poetry';
Person.address.home; // home
利用call和apply继承
function Parent(){
this.name = "abc";
this.address = {home: "home"};
}
function Child(){
Parent.call(this);
this.language = "js";
}
ES5中的Object.create()
var p = { name : 'poetry'};
var obj = Object.create(p);
obj.name; // poetry
Object.create()
作为new操作符的替代方案是ES5之后才出来的。我们也可以自己模拟该方法:
//模拟Object.create()方法
function myCreate(o){
function F(){};
F.prototype = o;
o = new F();
return o;
}
var p = { name : 'poetry'};
var obj = myCreate(p);
obj.name; // poetry
目前,各大浏览器的最新版本(包括IE9)都部署了这个方法。如果遇到老式浏览器,可以用下面的代码自行部署
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
2. 类的继承
Object.create()
function Person(name, age){}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){
console.log('eating...');
}
function Programmer(name, age, title){}
Programmer.prototype = Object.create(Person.prototype); //建立继承关系
Programmer.prototype.constructor = Programmer; // 修改constructor的指向
调用父类方法
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.headCount = 1;
Person.prototype.eat = function(){
console.log('eating...');
}
function Programmer(name, age, title){
Person.apply(this, arguments); // 调用父类的构造器
}
Programmer.prototype = Object.create(Person.prototype);
Programmer.prototype.constructor = Programmer;
Programmer.prototype.language = "js";
Programmer.prototype.work = function(){
console.log('i am working code in '+ this.language);
Person.prototype.eat.apply(this, arguments); // 调用父类上的方法
}
3. 封装
var app = {}; // 命名空间app
//模块1
app.module1 = {
name: 'poetry',
f: function(){
console.log('hi robot');
}
};
app.module1.name; // "poetry"
app.module1.f(); // hi robot
对象的属性外界是可读可写 如何来达到封装的额目的?答:可通过
闭包+局部变量
来完成
function Girl(name,age){
var love = '小明';//love 是局部变量 准确说不属于对象 属于这个函数的额激活对象 函数调用时必将产生一个激活对象 love在激活对象身上 激活对象有作用域的关系 有办法访问 加一个函数提供外界访问
this.name = name;
this.age = age;
this.say = function () {
return love;
};
this.movelove = function (){
love = '小轩'; //35
}
}
var g = new Girl('yinghong',22);
console.log(g);
console.log(g.say());//小明
console.log(g.movelove());//undefined 因为35行没有返回
console.log(g.say());//小轩
function fn(){
function t(){
//var age = 22;//声明age变量 在t的激活对象上
age = 22;//赋值操作 t的激活对象上找age属性 ,找不到 找fn的激活对象....再找到 最终找到window.age = 22;
//不加var就是操作window全局属性
}
t();
}
console.log(fn());//undefined
4. 静态成员
面向对象中的静态方法-静态属性:没有new对象 也能引用静态方法属性
function Person(name){
var age = 100;
this.name = name;
}
//静态成员
Person.walk = function(){
console.log('static');
};
Person.walk(); // static
5. 私有与公有
function Person(id){
// 私有属性与方法
var name = 'poetry';
var work = function(){
console.log(this.id);
};
//公有属性与方法
this.id = id;
this.say = function(){
console.log('say hello');
work.call(this);
};
};
var p1 = new Person(123);
p1.name; // undefined
p1.id; // 123
p1.say(); // say hello 123
6. 模块化
var moduleA;
moduleA = function() {
var prop = 1;
function func() {}
return {
func: func,
prop: prop
};
}(); // 立即执行匿名函数
7. 多态
多态:同一个父类继承出来的子类各有各的形态
function Cat(){
this.eat = '肉';
}
function Tiger(){
this.color = '黑黄相间';
}
function Cheetah(){
this.color = '报文';
}
function Lion(){
this.color = '土黄色';
}
Tiger.prototype = Cheetah.prototype = Lion.prototype = new Cat();//共享一个祖先 Cat
var T = new Tiger();
var C = new Cheetah();
var L = new Lion();
console.log(T.color);
console.log(C.color);
console.log(L.color);
console.log(T.eat);
console.log(C.eat);
console.log(L.eat);
8. 抽象类
在构造器中
throw new Error('')
; 抛异常。这样防止这个类被直接调用
function DetectorBase() {
throw new Error('Abstract class can not be invoked directly!');
}
DetectorBase.prototype.detect = function() {
console.log('Detection starting...');
};
DetectorBase.prototype.stop = function() {
console.log('Detection stopped.');
};
DetectorBase.prototype.init = function() {
throw new Error('Error');
};
// var d = new DetectorBase();
// Uncaught Error: Abstract class can not be invoked directly!
function LinkDetector() {}
LinkDetector.prototype = Object.create(DetectorBase.prototype);
LinkDetector.prototype.constructor = LinkDetector;
var l = new LinkDetector();
console.log(l); //LinkDetector {}__proto__: LinkDetector
l.detect(); //Detection starting...
l.init(); //Uncaught Error: Error
fn.length
是个不变的常数)// 写法1-不保存参数,递归局部函数
function curry(fn) {
let judge = (...args) => {
// 递归结束条件
if(args.length === fn.length) return fn(...args);
return (...arg) => judge(...args, ...arg);
}
return judge;
}
// 写法2-保存参数,递归整体函数
function curry(fn) {
// 保存参数,除去第一个函数参数
let presentArgs = [].slice.call(arguments, 1);
// 返回一个新函数
return function(){
// 新函数调用时会继续传参
let allArgs = [...presentArgs, ...arguments];
// 递归结束条件
if(allArgs.length === fn.length) {
// 如果参数够了,就执行原函数
return fn(,,,allArgs);
}
// 否则继续柯里化
else return curry(fn, ...allArgs);
}
}
// 测试
function add(a, b, c, d) {
return a + b + c + d;
}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
// 以下结果都返回 10
console.log(addCurry(1)(2)(3)(4));
console.log(addCurry(1)(2, 3, 4));
console.log(addCurry(1, 2)(3)(4));
console.log(addCurry(1, 2)(3, 4));
console.log(addCurry(1, 2, 3)(4));
console.log(addCurry(1, 2, 3, 4));
clear:both
的空 div
元素<div class="container">
<div class="left"></div>
<div class="right"></div>
<div style="clear:both"></div>
</div>
overflow:hidden
或者 auto
样式,触发BFC
<div class="container">
<div class="left"></div>
<div class="right"></div>
</div>
.container{
width: 300px;
background-color: #aaa;
overflow:hidden;
zoom:1; /*IE6*/
}
clear: both
属性的元素实现的。<div class="container clearfix">
<div class="left"></div>
<div class="right"></div>
</div>
.clearfix{
zoom: 1; /*IE6*/
}
.clearfix:after{
content: ".";
height: 0;
clear: both;
display: block;
visibility: hidden;
}
推荐使用第三种方法,不会在页面新增div,文档结构更加清晰
什么叫函数柯里化?其实就是将使用多个参数的函数转换成一系列使用一个参数的函数的技术。还不懂?来举个例子。
function add(a, b, c) {
return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)
现在就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。
function curry(fn) {
let judge = (...args) => {
if (args.length == fn.length) return fn(...args)
return (...arg) => judge(...args, ...arg)
}
return judge
}
// slice(start,[end])
// slice(start,[end])方法:该方法是对数组进行部分截取,该方法返回一个新数组
// 参数start是截取的开始数组索引,end参数等于你要取的最后一个字符的位置值加上1(可选)。
// 包含了源函数从start到 end 所指定的元素,但是不包括end元素,比如a.slice(0,3);
// 如果出现负数就把负数与长度相加后再划分。
// slice中的负数的绝对值若大于数组长度就会显示所有数组
// 若参数只有一个,并且参数大于length,则为空。
// 如果结束位置小于起始位置,则返回空数组
// 返回的个数是end-start的个数
// 不会改变原数组
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 从下标为0的到3,截取3之后的数console.log(arr.slice(0,3))//[1,2,3] 从下标为0的地方截取到下标为3之前的数console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 个人总结:slice的参数如果是正数就从左往右数,如果是负数的话就从右往左边数,
// 截取的数组与数的方向一致,如果是2个参数则截取的是数的交集,没有交集则返回空数组
// ps:slice也可以切割字符串,用法和数组一样,但要注意空格也算字符
// splice(start,deletecount,item)
// start:起始位置
// deletecount:删除位数
// item:替换的item
// 返回值为被删除的字符串
// 如果有额外的参数,那么item会插入到被移除元素的位置上。
// splice:移除,splice方法从array中移除一个或多个数组,并用新的item替换它们。
//举一个简单的例子
var a=['a','b','c'];
var b=a.splice(1,1,'e','f');
console.log(a) //['a', 'e', 'f', 'c']
console.log(b) //['b']
var a = [1, 2, 3, 4, 5, 6];
//console.log("被删除的为:",a.splice(1, 1, 8, 9)); //被删除的为:2
// console.log("a数组元素:",a); //1,8,9,3,4,5,6
// console.log("被删除的为:", a.splice(0, 2)); //被删除的为:1,2
// console.log("a数组元素:", a) //3,4,5,6
console.log("被删除的为:", a.splice(1, 0, 2, 2)) //插入 第二个数为0,表示删除0个
console.log("a数组元素:", a) //1,2,2,2,3,4,5,6
// split(字符串)
// string.split(separator,limit):split方法把这个string分割成片段来创建一个字符串数组。
// 可选参数limit可以限制被分割的片段数量。
// separator参数可以是一个字符串或一个正则表达式。
// 如果separator是一个空字符,会返回一个单字符的数组,不会改变原数组。
var a="0123456";
var b=a.split("",3);
console.log(b);//b=["0","1","2"]
// 注意:String.split() 执行的操作与 Array.join 执行的操作是相反的。
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}
测试:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
name: '布兰',
age: 12
}
render(template, person); // 我是布兰,年龄12,性别undefined
描述:只要promises
中有一个率先改变状态,就返回这个率先改变的Promise
实例的返回值。
实现:
Promise.race = function(promises){
return new Promise((resolve, reject) => {
if(Array.isArray(promises)) {
if(promises.length === 0) return resolve(promises);
promises.forEach((item) => {
Promise.resolve(item).then(
value => resolve(value),
reason => reject(reason)
);
})
}
else return reject(new TypeError("Argument is not iterable"));
});
}
描述:所有 promise
的状态都变成 fulfilled
,就会返回一个状态为 fulfilled
的数组(所有promise
的 value
)。只要有一个失败,就返回第一个状态为 rejected
的 promise
实例的 reason
。
实现:
Promise.all = 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] = value;
if(count === promises.length) resolve(result);
},
reason => reject(reason)
);
})
}
else return reject(new TypeError("Argument is not iterable"));
});
}
Iterator
(迭代器)是一种接口,也可以说是一种规范。为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator
接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator语法:
const obj = {
[Symbol.iterator]:function(){}
}
[Symbol.iterator]
属性名是固定的写法,只要拥有了该属性的对象,就能够用迭代器的方式进行遍历。
next
都会返回一个对象,该对象有两个属性value
代表想要获取的数据done
布尔值,false表示当前指针指向的数据有值,true表示遍历已经结束Iterator 的作用有三个:
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next()) // { value: Object { num: 1 }, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: 3, done: false }
console.log(it.next()) // { value: undefined, done: true }
对象没有布局Iterator接口,无法使用
for of
遍历。下面使得对象具备Iterator接口
为什么对象(Object)没有部署Iterator接口呢?
Iterator
接口并不是很必要,因为Map
弥补了它的缺陷,又正好有Iteraotr
接口let obj = {
id: '123',
name: '张三',
age: 18,
gender: '男',
hobbie: '睡觉'
}
obj[Symbol.iterator] = function () {
let keyArr = Object.keys(obj)
let index = 0
return {
next() {
return index < keyArr.length ? {
value: {
key: keyArr[index],
val: obj[keyArr[index++]]
}
} : {
done: true
}
}
}
}
for (let key of obj) {
console.log(key)
}
=>
定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。function foo() {
return (a) => {
console.log(this.a);
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1);
bar.call(obj2);
性能优化是前端开发中避不开的问题,
性能问题无外乎两方面原因:渲染速度慢、请求时间长
。性能优化虽然涉及很多复杂的原因和解决方案,但其实只要通过合理地使用标签,就可以在一定程度上提升渲染速度以及减少请求时间
1. script 标签:调整加载顺序提升渲染速度
渲染引擎在解析 HTML 时,若遇到 script 标签引用文件,则会暂停解析过程
,同时通知网络线程加载文件,文件加载后会切换至 JavaScript 引擎来执行对应代码
,代码执行完成之后切换至渲染引擎继续渲染页面
。页面渲染过程中包含了请求文件以及执行文件的时间
,但页面的首次渲染可能并不依赖这些文件,这些请求和执行文件的动作反而延长了用户看到页面的时间,从而降低了用户体验。为了减少这些时间损耗,可以借助 script 标签的 3 个属性来实现。
async 属性
。立即请求文件,但不阻塞渲染引擎,而是文件加载完毕后阻塞渲染引擎并立即执行文件内容
defer 属性
。立即请求文件,但不阻塞渲染引擎,等到解析完 HTML 之后再执行
文件内容绿色的线表示执行解析 HTML ,蓝色的线表示请求文件,红色的线表示执行文件
当渲染引擎解析 HTML 遇到 script 标签引入文件时,会立即进行一次渲染
。所以这也就是为什么构建工具会把编译好的引用 JavaScript 代码的 script 标签放入到 body 标签底部,因为当渲染引擎执行到 body 底部时会先将已解析的内容渲染出来,然后再去请求相应的 JavaScript 文件
2. link 标签:通过预处理提升渲染速度
在我们对大型单页应用进行性能优化时,也许会用到按需懒加载的方式,来加载对应的模块,但如果能合理利用 link
标签的 rel
属性值来进行预加载,就能进一步提升渲染速度。
dns-prefetch
。当 link
标签的 rel
属性值为“dns-prefetch”时,浏览器会对某个域名预先进行 DNS 解析并缓存
。这样,当浏览器在请求同域名资源的时候,能省去从域名查询 IP 的过程,从而减少时间损耗
。下图是淘宝网设置的 DNS 预解析 preconnect
。让浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括DNS 解析、TLS 协商、TCP 握手
,通过消除往返延迟来为用户节省时间prefetch/preload
。两个值都是让浏览器预先下载并缓存某个资源
,但不同的是,prefetch 可能会在浏览器忙时被忽略
,而 preload 则是一定会被预先下载
。prerender
。浏览器不仅会加载资源,还会解析执行页面,进行预渲染这几个属性值恰好反映了浏览器获取资源文件的过程,在这里我绘制了一个流程简图,方便你记忆。
3. 搜索优化
<meta name="description" content="全球最大的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。">
题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
实现代码如下:
function currying(fn, ...args) {
const length = fn.length;
let allArgs = [...args];
const res = (...newArgs) => {
allArgs = [...allArgs, ...newArgs];
if (allArgs.length === length) {
return fn(...allArgs);
} else {
return res;
}
};
return res;
}
// 用法如下:
// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。