乐趣区

关于javascript:前端高频面试题

说一说 js 是什么语言

JavaScript 是一种直译式脚本语言,是一种动静类型、弱类型、基于原型的语言,内置反对类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,宽泛用于客户端的脚本语言,最早是在 HTML(规范通用标记语言下的一个利用)网页上应用,用来给 HTML 网页减少动静性能。js 语言是弱语言类型,因而咱们在我的项目开发中当咱们随便更该某个变量的数据类型后
有可能会导致其余援用这个变量的办法中报错等等。复制代码

对 CSSSprites 的了解

CSSSprites(精灵图),将一个页面波及到的所有图片都蕴含到一张大图中去,而后利用 CSS 的 background-image,background-repeat,background-position 属性的组合进行背景定位。

长处:

  • 利用 CSS Sprites 能很好地缩小网页的 http 申请,从而大大提高了页面的性能,这是 CSS Sprites 最大的长处;
  • CSS Sprites能缩小图片的字节,把 3 张图片合并成 1 张图片的字节总是小于这 3 张图片的字节总和。

毛病:

  • 在图片合并时,要把多张图片有序的、正当的合并成一张图片,还要留好足够的空间,避免板块内呈现不必要的背景。在宽屏及高分辨率下的自适应页面,如果背景不够宽,很容易呈现背景断裂;
  • CSSSprites在开发的时候相对来说有点麻烦,须要借助 photoshop 或其余工具来对每个背景单元测量其精确的地位。
  • 保护方面:CSS Sprites在保护的时候比拟麻烦,页面背景有少许改变时,就要改这张合并的图片,无需改的中央尽量不要动,这样防止改变更多的CSS,如果在原来的中央放不下,又只能(最好)往下加图片,这样图片的字节就减少了,还要改变CSS

typeof null 的后果是什么,为什么?

typeof null 的后果是 Object。

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元蕴含一个小的 类型标签(1-3 bits) 以及以后要存储值的实在数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 以后存储的数据指向一个对象。1: int      - 以后存储的数据是一个 31 位的有符号整数。010: double   - 以后存储的数据指向一个双精度的浮点数。100: string   - 以后存储的数据指向一个字符串。110: boolean  - 以后存储的数据是布尔值。复制代码

如果最低位是 1,则类型标签标记位的长度只有一位;如果最低位是 0,则类型标签标记位的长度占三位,为存储其余四种数据类型提供了额定两个 bit 的长度。

有两种非凡数据类型:

  • undefined 的值是 (-2)30(一个超出整数范畴的数字);
  • null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被断定为 Object。

await 到底在等啥?

await 在期待什么呢? 一般来说,都认为 await 是在期待一个 async 函数实现。不过按语法阐明,await 期待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有非凡限定)。

因为 async 函数返回一个 Promise 对象,所以 await 能够用于期待一个 async 函数的返回值——这也能够说是 await 在等 async 函数,但要分明,它等的理论是一个返回值。留神到 await 不仅仅用于等 Promise 对象,它能够等任意表达式的后果,所以,await 前面理论是能够接一般函数调用或者间接量的。所以上面这个示例齐全能够正确运行:

function getSomething() {return "something";}
async function testAsync() {return Promise.resolve("hello async");
}
async function test() {const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}
test();
复制代码

await 表达式的运算后果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算后果就是它等到的货色。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞前面的代码,等着 Promise 对象 resolve,而后失去 resolve 的值,作为 await 表达式的运算后果。

来看一个例子:

function testAsy(x){return new Promise(resolve=>{setTimeout(() => {resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){let result =  await testAsy('hello world');
  console.log(result);    // 3 秒钟之后呈现 hello world
  console.log('cuger')   // 3 秒钟之后呈现 cug
}
testAwt();
console.log('cug')  // 立刻输入 cug
复制代码

这就是 await 必须用在 async 函数中的起因。async 函数调用不会造成阻塞,它外部所有的阻塞都被封装在一个 Promise 对象中异步执行。await 暂停以后 async 的执行,所以 ’cug” 最先输入,hello world’ 和‘cuger’是 3 秒钟后同时呈现的。

Proxy 能够实现什么性能?

在 Vue3.0 中通过 Proxy 来替换本来的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。

let p = new Proxy(target, handler)
复制代码

target 代表须要增加代理的对象,handler 用来自定义对象中的操作,比方能够用来自定义 set 或者 get 函数。

上面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {get(target, property, receiver) {getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = {a: 1}
let p = onWatch(
  obj,
  (v, property) => {console.log(` 监听到属性 ${property}扭转为 ${v}`)
  },
  (target, property) => {console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性 a 扭转
p.a // 'a' = 2
复制代码

在上述代码中,通过自定义 setget 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。

什么是 JavaScript 中的包装类型?

在 JavaScript 中,根本类型是没有属性和办法的,然而为了便于操作根本类型的值,在调用根本类型的属性或办法时 JavaScript 会在后盾隐式地将根本类型的值转换为对象,如:

const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
复制代码

在拜访 'abc'.length 时,JavaScript 将 'abc' 在后盾转换成 String('abc'),而后再拜访其length 属性。

JavaScript 也能够应用 Object 函数显式地将根本类型转换为包装类型:

var a = 'abc'
Object(a) // String {"abc"}
复制代码

也能够应用 valueOf 办法将包装类型倒转成根本类型:

var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
复制代码

看看如下代码会打印出什么:

var a = new Boolean(false);
if (!a) {console.log( "Oops"); // never runs
}
复制代码

答案是什么都不会打印,因为尽管包裹的根本类型是 false,然而false 被包裹成包装类型后就成了对象,所以其非值为false,所以循环体中的内容不会运行。

箭头函数和一般函数有什么区别?

(1)箭头函数比一般函数更加简洁
    如果没有参数,就间接写一个空括号即可
    如果只有一个参数,能够省去参数括号
    如果有多个参数,用逗号宰割
    如果函数体的返回值只有一句,能够省略大括号
    如果函数体不须要返回值,且只有一句话,能够给这个语句后面加一个 void 关键字。最罕用的就是调用一个函数:let fn = () => void doesNotReturn()

 (2) 箭头函数没有本人的 this
 箭头函数不会创立本人的 this, 所以它没有本人的 this, 它只会在本人作用域的上一层继承 this。所以箭头函数中的 this 的指向在它在定义时一家确定了,之后不会扭转。(3)箭头函数继承来的 this 指向永远不会扭转

 (4) call()、apply()、bind()等办法不能扭转箭头函数中的 this 指向 

 (5) 箭头函数不能作为构造函数应用

 (6) 箭头函数没有本人的 arguments

 (7) 箭头函数没有 prototype

 (8) 箭头函数不能用作 Generator 函数, 不能应用 yeild 关键字
复制代码

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要 思考浏览器兼容问题。
.parent {position: relative;} .child {position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
复制代码
  • 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于 盒子有宽高 的状况:
.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
复制代码
  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于 盒子宽高已知 的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
复制代码
  • 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 思考兼容的问题,该办法在挪动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
复制代码

JavaScript 有哪些数据类型,它们的区别?

JavaScript 共有八种数据类型,别离是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。

其中 Symbol 和 BigInt 是 ES6 中新增的数据类型:

  • Symbol 代表创立后举世无双且不可变的数据类型,它次要是为了解决可能呈现的全局变量抵触的问题。
  • BigInt 是一种数字类型的数据,它能够示意任意精度格局的整数,应用 BigInt 能够平安地存储和操作大整数,即便这个数曾经超出了 Number 可能示意的平安整数范畴。

这些数据能够分为原始数据类型和援用数据类型:

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
  • 堆:援用数据类型(对象、数组和函数)

两种类型的区别在于 存储地位的不同:

  • 原始数据类型间接存储在栈(stack)中的简略数据段,占据空间小、大小固定,属于被频繁应用数据,所以放入栈中存储;
  • 援用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;援用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找援用值时,会首先检索其在栈中的地址,获得地址后从堆中取得实体。

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取形式为先进后出。
  • 堆是一个优先队列,是按优先级来进行排序的,优先级能够依照大小来规定。

在操作系统中,内存被分为栈区和堆区:

  • 栈区内存由编译器主动调配开释,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。
  • 堆区内存个别由开发着调配开释,若开发者不开释,程序完结时可能由垃圾回收机制回收。

typeof NaN 的后果是什么?

NaN 指“不是一个数字”(not a number),NaN 是一个“戒备值”(sentinel value,有非凡用处的惯例值),用于指出数字类型中的谬误状况,即“执行数学运算没有胜利,这是失败后返回的后果”。

typeof NaN; // "number"
复制代码

NaN 是一个非凡值,它和本身不相等,是惟一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。

常见的程度垂直形式有几种?

// 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要思考浏览器兼容问题。.parent {position: relative;}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
// 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于盒子有宽高的状况:.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
// 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于盒子宽高已知的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
// 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 ** 思考兼容的问题 **,该办法在挪动端用的较多:.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
// 另外,如果父元素设置了 flex 布局,只须要给子元素加上 `margin:auto;` 就能够实现垂直居中布局
.parent{display:flex;}
.child{margin: auto;}
复制代码

vue-router

vue-router 是 vuex.js 官网的路由管理器,它和 vue.js 的外围深度集成,让构建但页面利用变得大海捞针

<router-link> 组件反对用户在具备路由性能的利用中 (点击) 导航。通过 to 属性指定指标地址

<router-view> 组件是一个 functional 组件,渲染门路匹配到的视图组件。<keep-alive> 组件是一个用来缓存组件

router.beforeEach

router.afterEach

to: Route: 行将要进入的指标 路由对象

from: Route: 以后导航正要来到的路由

next: Function: 肯定要调用该办法来 resolve 这个钩子。执行成果依赖 next 办法的调用参数。介绍了路由守卫及用法,在我的项目中路由守卫起到的作用等等
复制代码

实现数组原型办法

forEach

语法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

参数:

callback:为数组中每个元素执行的函数,该函数承受 1 - 3 个参数currentValue: 数组中正在解决的以后元素index(可选): 数组中正在解决的以后元素的索引array(可选): forEach() 办法正在操作的数组 thisArg(可选): 当执行回调函数 callback 时,用作 this 的值。

返回值:undefined

Array.prototype.forEach1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    // 创立一个新的 Object 对象。该对象将会包裹 (wrapper) 传入的参数 this(以后数组)。const O = Object(this);
    // O.length >>> 0 无符号右移 0 位
    // 意义:为了保障转换后的值为正整数。// 其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}
复制代码

map

语法:arr.map(callback(currentValue [, index [, array]])[, thisArg])

参数:与 forEach() 办法一样

返回值:一个由原数组每个元素执行回调函数的后果组成的新数组。

Array.prototype.map1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {newArr[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
    return newArr;
}
复制代码

filter

语法:arr.filter(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。返回 true 示意该元素通过测试,保留该元素,false 则不保留。它承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。

返回值:一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

Array.prototype.filter1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {newArr.push(O[k]);
            }
        }
        k++;
    }
    return newArr;
}
复制代码

some

语法:arr.some(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。
返回值:数组中有至多一个元素通过回调函数的测试就会返回 true;所有元素都没有通过回调函数的测试返回值才会为 false

Array.prototype.some1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {return true}
        }
        k++;
    }
    return false;
}
复制代码

reduce

语法:arr.reduce(callback(preVal, curVal[, curIndex [, array]])[, initialValue])

参数:

callback: 一个“reducer”函数,蕴含四个参数:

preVal:上一次调用 callback 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]

curVal:数组中正在解决的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]

curIndex(可选):数组中正在解决的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。

array(可选):用于遍历的数组。
initialValue(可选): 作为第一次调用 callback 函数时参数 preVal 的值。若指定了初始值 initialValue,则 curVal 则将应用数组第一个元素;否则 preVal 将应用数组第一个元素,而 curVal 将应用数组第二个元素。
返回值:应用“reducer”回调函数遍历整个数组后的后果。

Array.prototype.reduce1 = function(callback, initialValue) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this);
    const len = O.length >>> 0;
    let k = 0;
    let accumulator = initialValue;
    // 如果第二个参数为 undefined 的状况下,则数组的第一个有效值(非 empty)作为累加器的初始值
    if(accumulator === undefined) {while(k < len && !(k in O)) {k++;}
        // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
        if(k >= len) {throw new TypeError('Reduce of empty array with no initial value');
        }
        accumulator = O[k++];
    }
    while(k < len) {if(k in O) {accumulator = callback(accumulator, O[k], k, O);
        }
        k++;
    }
    return accumulator;
}
复制代码

Promise 是什么,解决了什么,之前怎么实现的

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。解决来之前在申请中回调申请产生的回调天堂,使得当初的代码更加正当更加优雅,也更加容易定位查找问题。复制代码

原函数形参定长(此时 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));
复制代码

说一下 vue3.0 你理解多少?

 <!-- 响应式原理的扭转 Vue3.x 应用 Proxy 取代 Vue2.x 版本的 Object.defineProperty -->
 <!-- 组件选项申明形式 Vue3.x 应用 Composition API setup 是 Vue3.x 新增的一个选项,他
    是组件内应用 Composition API 的入口 -->
 <!-- 模板语法变动 slot 具名插槽语法 自定义指令 v-model 降级 -->
 <!-- 其它方面的更改 Suspense 反对 Fragment(多个根节点) 和 Protal (在 dom 其余局部渲染组建内容)组件
     针对一些非凡的场景做了解决。基于 treeshaking 优化,提供了更多的内置性能。-->
复制代码

如何优化要害渲染门路?

为尽快实现首次渲染,咱们须要最大限度减小以下三种可变因素:

(1)要害资源的数量。

(2)要害门路长度。

(3)关键字节的数量。

要害资源是可能阻止网页首次渲染的资源。这些资源越少,浏览器的工作量就越小,对 CPU 以及其余资源的占用也就越少。同样,要害门路长度受所有要害资源与其字节大小之间依赖关系图的影响:某些资源只能在上一资源处理完毕之后能力开始下载,并且资源越大,下载所需的往返次数就越多。最初,浏览器须要下载的关键字节越少,解决内容并让其呈现在屏幕上的速度就越快。要缩小字节数,咱们能够缩小资源数(将它们删除或设为非关键资源),此外还要压缩和优化各项资源,确保最大限度减小传送大小。

优化要害渲染门路的惯例步骤如下:

(1)对要害门路进行剖析和个性形容:资源数、字节数、长度。

(2)最大限度缩小要害资源的数量:删除它们,提早它们的下载,将它们标记为异步等。

(3)优化要害字节数以缩短下载工夫(往返次数)。

(4)优化其余要害资源的加载程序:您须要尽早下载所有要害资产,以缩短要害门路长度

什么是物理像素,逻辑像素和像素密度,为什么在挪动端开发时须要用到 @3x, @2x 这种图片?

以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV 元素宽度为 414px,这个 DIV 就会填满手机的宽度;

而如果有一把尺子来理论测量这部手机的物理像素,理论为 1242*2688 物理像素;通过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素 = 3 个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。

对于图片来说,为了保障其不失真,1 个图片像素至多要对应一个物理像素,如果原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片能力保障 1 个物理像素至多对应一个图片像素,能力不失真。当然,也能够针对所有屏幕,都只提供最高清图片。尽管低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽节约和下载提早,但从后果上说能保障图片在所有屏幕上都不会失真。

还能够应用 CSS 媒体查问来判断不同的像素密度,从而抉择不同的图片:

my-image {background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {#my-image { background: (high.png); }
}
复制代码

Object.is() 与比拟操作符“===”、“==”的区别?

  • 应用双等号(==)进行相等判断时,如果两边的类型不统一,则会进行强制类型转化后再进行比拟。
  • 应用三等号(===)进行相等判断时,如果两边的类型不统一时,不会做强制类型准换,间接返回 false。
  • 应用 Object.is 来进行相等判断时,个别状况下和三等号的判断雷同,它解决了一些非凡的状况,比方 -0 和 +0 不再相等,两个 NaN 是相等的。
退出移动版