🌈个人主页:前端青山
🔥系列专栏:JavaScript篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript全面指南
目录
81、ES6 class关键字原理跟function什么区别
82、如何检查一个数字是否为整数?
83、 什么是 IIFE(立即调用函数表达式)?
84、如何在 JavaScript 中比较两个对象?
85、 你能解释一下 ES5 和 ES6 之间的区别吗?
86、 解释 JavaScript 中“undefined”和“not defined”之间的区别。
87、如何在 JavaScript 中创建私有变量?
88、 请解释原型设计模式。
89、模板引擎原理
90、 map和foreach的区别
91、es6的新特性
92、如何向 Array 对象添加自定义方法,让下面的代码可以运行?
93、0.1+0.2等不等于0.3?自己封装一个让他们相等的方法
94、跨域是什么?有哪些解决跨域的方法和方案?
95、什么是函数式编程?什么的声明式编程?
96、super() 是否必须执行?不执行怎么让它不报错?
97、什么是 JavaScript 中的提升操作?
98、 以下代码输出的结果是什么?
99、图片懒加载怎么实现
100、for-in 循环会遍历出原型上的属性吗?
81、ES6 class关键字原理跟function什么区别
传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。这样的写法相对于其它传统面向对象语言来讲,很有一种独树一帜的感脚!非常容易让人困惑! 如果要生成一个对象实例,需要先定义一个构造函数,然后通过new操作符来完成。构造函数示例:
//函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)
function Person(name,age) {
this.name = name;
this.age=age;
}
Person.prototype.say = function(){
return "我的名字叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("laotie",88);//通过构造函数创建对象,必须使用new 运算符
console.log(obj.say());//我的名字叫laotie今年88岁了
构造函数生成实例的执行过程:
1.当使用了构造函数,并且new 构造函数(),后台会隐式执行new Object()创建对象; 2.将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object()出来的对象。 3.执行构造函数的代码。 4.返回新对象(后台直接返回);
ES6引入了Class(类)这个概念,通过class关键字可以定义类。该关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言。如果将之前的代码改为ES6的写法就会是这个样子:
class Person{//定义了一个名字为Person的类
constructor(name,age){//constructor是一个构造方法,用来接收参数
this.name = name;//this代表的是实例对象
this.age=age;
}
say(){//这是一个类的方法,注意千万不要加上function
return "我的名字叫" + this.name+"今年"+this.age+"岁了";
}
}
var obj=new Person("laotie",88);
console.log(obj.say());//我的名字叫laotie今年88岁了
注意项
1.在类中声明方法的时候,千万不要给该方法加上function关键字 2.方法之间不要用逗号分隔,否则会报错
由下面代码可以看出类实质上就是一个函数。类自身指向的就是构造函数。所以可以认为ES6中的类其实就是构造函数的另外一种写法!
console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true
以下代码说明构造函数的prototype属性,在ES6的类中依然存在着。 console.log(Person.prototype);//输出的结果是一个对象
实际上类的所有方法都定义在类的prototype属性上。代码证明下:
Person.prototype.say=function(){//定义与类中相同名字的方法。成功实现了覆盖!
return "我是来证明的,你叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("laotie",88);
console.log(obj.say());//我是来证明的,你叫laotie今年88岁了
当然也可以通过prototype属性对类添加方法。如下:
Person.prototype.addFn=function(){
return "我是通过prototype新增加的方法,名字叫addFn";
}
var obj=new Person("laotie",88);
console.log(obj.addFn());//我是通过prototype新增加的方法,名字叫addFn
还可以通过Object.assign方法来为对象动态增加方法
Object.assign(Person.prototype,{
getName:function(){
return this.name;
},
getAge:function(){
return this.age;
}
})
var obj=new Person("laotie",88);
console.log(obj.getName());//laotie
console.log(obj.getAge());//88
constructor方法是类的构造函数的默认方法,通过new命令生成对象实例时,自动调用该方法。
class Box{
constructor(){
console.log("啦啦啦,今天天气好晴朗");//当实例化对象时该行代码会执行。
}
}
var obj=new Box();
constructor方法如果没有显式定义,会隐式生成一个constructor方法。所以即使你没有添加构造函数,构造函数也是存在的。constructor方法默认返回实例对象this,但是也可以指定constructor方法返回一个全新的对象,让返回的实例对象不是该类的实例。
class Desk{
constructor(){
this.xixi="我是一只小小小小鸟!哦";
}
}
class Box{
constructor(){
return new Desk();// 这里没有用this哦,直接返回一个全新的对象
}
}
var obj=new Box();
console.log(obj.xixi);//我是一只小小小小鸟!哦
constructor中定义的属性可以称为实例属性(即定义在this对象上),constructor外声明的属性都是定义在原型上的,可以称为原型属性(即定义在class上)。hasOwnProperty()函数用于判断属性是否是实例属性。其结果是一个布尔值, true说明是实例属性,false说明不是实例属性。in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
class Box{
constructor(num1,num2){
this.num1 = num1;
this.num2=num2;
}
sum(){
return num1+num2;
}
}
var box=new Box(12,88);
console.log(box.hasOwnProperty("num1"));//true
console.log(box.hasOwnProperty("num2"));//true
console.log(box.hasOwnProperty("sum"));//false
console.log("num1" in box);//true
console.log("num2" in box);//true
console.log("sum" in box);//true
console.log("say" in box);//false
类的所有实例共享一个原型对象,它们的原型都是Person.prototype,所以proto属性是相等的
class Box{
constructor(num1,num2){
this.num1 = num1;
this.num2=num2;
}
sum(){
return num1+num2;
}
}
//box1与box2都是Box的实例。它们的__proto__都指向Box的prototype
var box1=new Box(12,88);
var box2=new Box(40,60);
console.log(box1.__proto__===box2.__proto__);//true
由此,也可以通过proto来为类增加方法。使用实例的proto属性改写原型,会改变Class的原始定义,影响到所有实例,所以不推荐使用!
class Box{
constructor(num1,num2){
this.num1 = num1;
this.num2=num2;
}
sum(){
return num1+num2;
}
}
var box1=new Box(12,88);
var box2=new Box(40,60);
box1.__proto__.sub=function(){
return this.num2-this.num1;
}
console.log(box1.sub());//76
console.log(box2.sub());//20
class不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。
//ES5可以先使用再定义,存在变量提升
new A();
function A(){
}
//ES6不能先使用再定义,不存在变量提升 会报错
new B();//B is not defined
class B{
}
82、如何检查一个数字是否为整数?
检查一个数字是小数还是整数,可以使用一种非常简单的方法,就是将它对 1 进行取模,看看是否有余数。
function isInt(num) {
return num % 1 === 0;
}
console.log(isInt(4)); // true
console.log(isInt(12.2)); // false
console.log(isInt(0.3)); // false
83、 什么是 IIFE(立即调用函数表达式)?
它是立即调用函数表达式(Immediately-Invoked Function Expression),简称 IIFE。函数被创建后立即被执行:
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"
在避免污染全局命名空间时经常使用这种模式,因为 IIFE(与任何其他正常函数一样)内部的所有变量在其作用域之外都是不可见的。
84、如何在 JavaScript 中比较两个对象?
对于两个非原始值,比如两个对象(包括函数和数组),== 和 === 比较都只是检查它们的引用是否匹配,并不会检查实际引用的内容。
例如,默认情况下,数组将被强制转型成字符串,并使用逗号将数组的所有元素连接起来。所以,两个具有相同内容的数组进行 == 比较时不会相等:
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false
对于对象的深度比较,可以使用 deep-equal 这个库,或者自己实现递归比较算法
85、 你能解释一下 ES5 和 ES6 之间的区别吗?
-
ECMAScript 5(ES5):ECMAScript 的第 5 版,于 2009 年标准化。这个标准已在所有现代浏览器中完全实现。
-
ECMAScript 6(ES6)或 ECMAScript 2015(ES2015):第 6 版 ECMAScript,于 2015 年标准化。这个标准已在大多数现代浏览器中部分实现。
以下是 ES5 和 ES6 之间的一些主要区别:
箭头函数和字符串插值:
const greetings = (name) => {
return `hello ${name}`;
}
const greetings = name => `hello ${name}`;
常量
常量在很多方面与其他语言中的常量一样,但有一些需要注意的地方。常量表示对值的“固定引用”。因此,在使用常量时,你实际上可以改变变量所引用的对象的属性,但无法改变引用本身。
const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // error
块作用域变量。
新的 ES6 关键字 let 允许开发人员声明块级别作用域的变量。let 不像 var 那样可以进行提升。
默认参数值
默认参数允许我们使用默认值初始化函数。如果省略或未定义参数,则使用默认值,也就是说 null 是有效值。
// 基本语法
function multiply (a, b = 2) {
return a * b;
}
multiply(5); // 10
类定义和继承
ES6 引入了对类(关键字 class)、构造函数(关键字 constructor)和用于继承的 extend 关键字的支持。
for…of 操作符
for…of 语句将创建一个遍历可迭代对象的循环。
用于对象合并的 Spread 操作
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2, c: 3, d: 4}
const obj3 = {...obj1, ...obj2}
promise
promise 提供了一种机制来处理异步操作结果。你可以使用回调来达到同样的目的,但是 promise 通过方法链接和简洁的错误处理带来了更高的可读性。
const isGreater = (a, b) => {
return new Promise ((resolve, reject) => {
if(a > b) {
resolve(true)
} else {
reject(false)
}
})
}
isGreater(1, 2)
.then(result => {
console.log('greater')
})
.catch(result => {
console.log('smaller')
})
模块导出和导入
const myModule = { x: 1, y: () => { console.log('This is ES5') }}
export default myModule;
import myModule from './myModule';
86、 解释 JavaScript 中“undefined”和“not defined”之间的区别。
在 JavaScript 中,如果你试图使用一个不存在且尚未声明的变量,JavaScript 将抛出错误“var name is not defined”,让后脚本将停止运行。但如果你使用 typeof undeclared_variable,它将返回 undefined。
在进一步讨论之前,先让我们理解声明和定义之间的区别。
“var x”表示一个声明,因为你没有定义它的值是什么,你只是声明它的存在。
var x; // 声明 x
console.log(x); // 输出: undefined
“var x = 1”既是声明又是定义(我们也可以说它是初始化),x 变量的声明和赋值相继发生。在 JavaScript 中,每个变量声明和函数声明都被带到了当前作用域的顶部,然后进行赋值,这个过程被称为提升(hoisting)。
当我们试图访问一个被声明但未被定义的变量时,会出现 undefined 错误。
var x; // 声明
if(typeof x === 'undefined') // 将返回 true
当我们试图引用一个既未声明也未定义的变量时,将会出现 not defined 错误。
console.log(y); // 输出: ReferenceError: y is not defined
87、如何在 JavaScript 中创建私有变量?
要在 JavaScript 中创建无法被修改的私有变量,你需要将其创建为函数中的局部变量。即使这个函数被调用,也无法在函数之外访问这个变量。例如:
function func() {
var priv = "secret code";
}
console.log(priv); // throws error
要访问这个变量,需要创建一个返回私有变量的辅助函数。
function func() {
var priv = "secret code";
return function() {
return priv;
}
}
var getPriv = func();
console.log(getPriv()); // => secret code
88、 请解释原型设计模式。
原型模式可用于创建新对象,但它创建的不是非初始化的对象,而是使用原型对象(或样本对象)的值进行初始化的对象。原型模式也称为属性模式。
原型模式在初始化业务对象时非常有用,业务对象的值与数据库中的默认值相匹配。原型对象中的默认值被复制到新创建的业务对象中。
经典的编程语言很少使用原型模式,但作为原型语言的 JavaScript 在构造新对象及其原型时使用了这个模式。
89、模板引擎原理
板引擎是通过字符串拼接得到的
let template = 'hello <% name %>!'
let template = 'hello ' + name + '!'
12
字符串是通过new Function
执行的
let name = 'world'
let template = `
let str = 'hello ' + name + '!'
return str
`
let fn = new Function('name', template)
console.log(fn(name)) // hello world!
1234567
将模板转换为字符串并通过函数执行返回
let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
return `' + ${code} + '`
})
html = `let str = '${html}'; return str`
return new Function('name', html)
}
let str = compile(template)
console.log(str(name)) // hello world!
1234567891011
函数只能接收一个
name
变量作为参数,功能太单一了,一般会通过对象来传参,with
来减少变量访问。
with功能
let params = {
name: '张三',
age: 18
}
let str = ''
with (params) {
str = `用户${name}的年龄是${age}岁`
}
console.log(str) // 用户张三的年龄是18岁
123456789
实现简单的模板引擎
let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
return `' + ${code.trim()} + '`
})
html = `'${html}'`
html = `let str = ''; with (params) { str = ${html}; } return str`
return new Function('params', html)
}
let str = compile(template)
console.log(str({ name })) // hello world!
90、 map和foreach的区别
-
都是循环遍历数组中的每一项
-
forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
-
匿名函数中的this都是指向window
-
只能遍历数组
array.map(function(item,index,arr){},this) Array.forEach(function(item,index,arr){},this)
二、区别:
1.forEach()
没有返回值。
2.map()
有返回值,可以return 出来。
var arr = [1,23,3];
arr.map(function(value,index,array){
//do something
return XXX
})
91、es6的新特性
1.不一样的变量声明:const和let
ES6推荐使用let声明局部变量,相比之前的var(无论声明在何处,都会被视为声明在函数的最顶部) let和var声明的区别:
var x = '全局变量';
{
let x = '局部变量';
console.log(x); // 局部变量
}
console.log(x); // 全局变量
let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:
const a = 1
a = 0 //报错
如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:
const student = { name: 'cc' }
student.name = 'yy';// 不报错
student = { name: 'yy' };// 报错
有几个点需要注意:
let 关键词声明的变量不具备变量提升(hoisting)特性
let 和 const 声明只在最靠近的一个块中(花括号内)有效
当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
const 在声明时必须被赋值
2.模板字符串
在ES6之前,我们往往这么处理模板字符串: 通过“\”和“+”来构建模板
$("body").html("This demonstrates the output of HTML \ content to the page, including student's\ " + name + ", " + seatNumber + ", " + sex + " and so on.");
而对ES6来说
-
基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
-
ES6反引号(``)直接搞定;
$("body").html(`This demonstrates the output of HTML content to the page,
including student's ${name}, ${seatNumber}, ${sex} and so on.`);
3.箭头函数(Arrow Functions)
ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;
箭头函数最直观的三个特点。
不需要 function 关键字来创建函数
省略 return 关键字
继承当前上下文的 this 关键字
// ES5
var add = function (a, b) {
return a + b;
};
// 使用箭头函数
var add = (a, b) => a + b;
// ES5
[1,2,3].map((function(x){
return x + 1;
}).bind(this));
// 使用箭头函数
[1,2,3].map(x => x + 1);
细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的。当你函数返回有且仅有一个表达式的时候可以省略{} 和 return;
-
函数的参数默认值
在ES6之前,我们往往这样定义参数的默认值:
// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
printText();// default
5.Spread / Rest 操作符
Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。
当被用于迭代器中时,它是一个 Spread 操作符:
function foo(x,y,z) {
console.log(x,y,z);
}
let arr = [1,2,3];
foo(...arr); // 1 2 3
当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:
function foo(...args) {
console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
6.二进制和八进制字面量
ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2
7.对象和数组解构
// 对象
const student = {
name: 'Sam',
age: 22,
sex: '男'
}
// 数组
// const student = ['Sam', 22, '男'];
// ES5;
const name = student.name;
const age = student.age;
const sex = student.sex;
console.log(name + ' --- ' + age + ' --- ' + sex);
// ES6
const { name, age, sex } = student;
console.log(name + ' --- ' + age + ' --- ' + sex);
8.对象超类
ES6 允许在对象中使用 super 方法:
var parent = {
foo() {
console.log("Hello from the Parent");
}
}
var child = {
foo() {
super.foo();
console.log("Hello from the Child");
}
}
Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
// Hello from the Child
9.for...of 和 for...in
for...of 用于遍历一个迭代器,如数组:
let letters = ['a', 'b', 'c'];
letters.size = 3;
for (let letter of letters) {
console.log(letter);
}
// 结果: a, b, c
for...in 用来遍历对象中的属性:
let stus = ["Sam", "22", "男"];
for (let stu in stus) {
console.log(stus[stu]);
}
// 结果: Sam, 22, 男
10.ES6中的类
ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
class Student {
constructor() {
console.log("I'm a student.");
}
study() {
console.log('study!');
}
static read() {
console.log("Reading Now.");
}
}
console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."
类中的继承和超集:
class Phone {
constructor() {
console.log("I'm a phone.");
}
}
class MI extends Phone {
constructor() {
super();
console.log("I'm a phone designed by xiaomi");
}
}
let mi8 = new MI();
extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。 当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。 在 这里 关于类的介绍。
有几点值得注意的是:
类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
在类中定义函数不需要使用 function 关键词
92、如何向 Array 对象添加自定义方法,让下面的代码可以运行?
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg);
JavaScript 不是基于类的,但它是基于原型的语言。这意味着每个对象都链接到另一个对象(也就是对象的原型),并继承原型对象的方法。你可以跟踪每个对象的原型链,直到到达没有原型的 null 对象。我们需要通过修改 Array 原型来向全局 Array 对象添加方法。
Array.prototype.average = function() {
// 计算 sum 的值
var sum = this.reduce(function(prev, cur) { return prev + cur; });
// 将 sum 除以元素个数并返回
return sum / this.length;
}
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg); // => 3
93、0.1+0.2等不等于0.3?自己封装一个让他们相等的方法
console.log(0.1+0.2===0.3)// true or false??
在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对JavaScript的数值的理解程度。
在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为false。
那么应该怎样来解决0.1+0.2等于0.3呢? 最好的方法是设置一个误差范围值,通常称为”机器精度“,而对于Javascript来说,这个值通常是2^-52,而在ES6中,已经为我们提供了这样一个
属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于
Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true。
function numbersequal(a,b){ return Math.abs(a-b)<Number.EPSILON;
}
var a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //true
但是这里要考虑兼容性的问题了,在chrome中支持这个属性,但是IE并不支持(笔者的版本是IE10不兼容),所以我们还要解决IE的不兼容问题。
Number.EPSILON=(function(){ //解决兼容性问题
return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
})();
//上面是一个自调用函数,当JS文件刚加载到内存中,就会去判断并返回一个结果,相比if(!Number.EPSILON){
// Number.EPSILON=Math.pow(2,-52);
//}这种代码更节约性能,也更美观。
function numbersequal(a,b){
return Math.abs(a-b)<Number.EPSILON;
}
//接下来再判断
var a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //这里就为true了
94、跨域是什么?有哪些解决跨域的方法和方案?
CORS PROXY JSONP
95、什么是函数式编程?什么的声明式编程?
什么是声明式编程?一般来说我们对于声明式的理解都是相对于命令式(imperative)而言的。图灵教会了我们imperative的真谛,并赋予了它数学意义上的精确定义:一台有状态的机器,根据明确的指令(instruction)一步步的执行。而所谓的声明式,它可以看作是命令式的反面。曾有人言:一切非imperative,皆是declarative。从这个意义上说,越是偏离图灵机的图像越远的,就越是声明式的。
所以,函数式编程(Functional Programming)是声明式的,因为它不使用可变状态,也不需要指定任何的执行顺序关系(可以假定所有的函数都是同时执行的,因为存在引用透明性,所谓的参数和变量都只是一堆符号的别名而已)。逻辑式编程(Logical Programming)也是声明式的,因为我们只需要通过facts和rules描述我们所需要解决的问题,具体的求解路径由编译器和程序运行时自动决定。
96、super() 是否必须执行?不执行怎么让它不报错?
构造函数有什么作用?创建类的对象,只有对象才能调用类的属性和方法。当子类继承父类的属性和方法时,如何去调用父类的构造函数?Super()当父类构造函数是空参数时,系统会默认添加Super(),此时你可以省略。如果父类是非参数时,必须要添加Super(),初始化父类,初始化父类,子类才能调用父类属性和方法。这就是Super()的作用。
97、什么是 JavaScript 中的提升操作?
提升(hoisting)是 JavaScript 解释器将所有变量和函数声明移动到当前作用域顶部的操作。有两种类型的提升:
-
变量提升——非常少见
-
函数提升——常见
无论 var(或函数声明)出现在作用域的什么地方,它都属于整个作用域,并且可以在该作用域内的任何地方访问它。
var a = 2;
foo(); // 因为`foo()`声明被"提升",所以可调用
function foo() {
a = 3;
console.log( a ); // 3
var a; // 声明被"提升"到 foo() 的顶部
}
console.log( a ); // 2
98、 以下代码输出的结果是什么?
0.1 + 0.2 === 0.3
这段代码的输出是 false,这是由浮点数内部表示导致的。0.1 + 0.2 并不刚好等于 0.3,实际结果是 0.30000000000000004。解决这个问题的一个办法是在对小数进行算术运算时对结果进行舍入。
99、图片懒加载怎么实现
一、什么是懒加载
将图片src先赋值为一张默认图片,当用户滚动滚动条到可视区域图片的时候,再去加载后续真正的图片
如果用户只对第一张图片感兴趣,那剩余的图片请求就可以节省了
二、为什么要引入懒加载
懒加载(LazyLoad)是前端优化的一种有效方式,极大的提升用户体验。图片一直是页面加载的流浪大户,现在一张图片几兆已经是很正常的事,远远大于代码的大小。倘若一次ajax请求10张图片的地址,一次性把10张图片都加载出来,肯定是不合理的。
三、懒加载实现的原理
先将img标签中的src链接设置为空,将真正的图片链接放在自定义属性(data-src),当js监听到图片元素进入到可视窗口的时候,将自定义属性中的地址存储到src中,达到懒加载的效果。
四、懒加载中涉及的属性
1 、document.documentElement.clientHeight; //表示浏览器可见区域高度:
document.body.clientHeight //是整个页面内容的高度,而非浏览器可见区域的高度
2 、document.documentElement.scrollTop; //滚动条 已滚动的高度:
chrome 中 document.body.scrollTop //滚动条 滚过的高度
那么要得到滚动条的高度:有一个技巧:
var scrollTop=document.body.scrollTop || document.documentElement.scrollTop;
这两个值总会有一个恒为0,所以不用担心会对真正的scrollTop造成影响。一点小技巧,但很实用。
3、 offsetTop、offsetLeft
obj.offsetTop 指 obj 距离上方或上层控件的位置,整型,单位像素。
obj.offsetLeft 指 obj 距离左方或上层控件的位置,整型,单位像素。
obj.offsetWidth 指 obj 控件自身的宽度,整型,单位像素。
obj.offsetHeight 指 obj 控件自身的高度,整型,单位像素。
offsetParent 不同于parentNode ,offsetParent 返回的是在结构层次中与这个元素最近的position为absolute\relative\static的元素或者body
具体滚动时涉及的属性值,请参考JS中offsetTop、clientTop、scrollTop、offsetTop各位置属性详解(含示例图)_scrolltop和offsettop-CSDN博客非常详细
五、懒加载的实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazyload 2</title>
<style>
img {
display: block;
margin-bottom: 50px;
height: 200px;
}
</style>
</head>
<body>
<img src="images/loading.gif" data-src="images/1.png">
<img src="images/loading.gif" data-src="images/2.png">
<img src="images/loading.gif" data-src="images/3.png">
<img src="images/loading.gif" data-src="images/4.png">
<img src="images/loading.gif" data-src="images/5.png">
<img src="images/loading.gif" data-src="images/6.png">
<img src="images/loading.gif" data-src="images/7.png">
<img src="images/loading.gif" data-src="images/8.png">
<img src="images/loading.gif" data-src="images/9.png">
<img src="images/loading.gif" data-src="images/10.png">
<img src="images/loading.gif" data-src="images/11.png">
<img src="images/loading.gif" data-src="images/12.png">
<script>
function throttle(fn, delay, atleast) {//函数绑定在 scroll 事件上,当页面滚动时,避免函数被高频触发,
var timeout = null,//进行去抖处理
startTime = new Date();
return function() {
var curTime = new Date();
clearTimeout(timeout);
if(curTime - startTime >= atleast) {
fn();
startTime = curTime;
}else {
timeout = setTimeout(fn, delay);
}
}
}
function lazyload() {
var images = document.getElementsByTagName('img');
var len = images.length;
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
return function() {
var seeHeight = document.documentElement.clientHeight;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(var i = n; i < len; i++) {
if(images[i].offsetTop < seeHeight + scrollTop) {
if(images[i].getAttribute('src') === 'images/loading.gif') {
images[i].src = images[i].getAttribute('data-src');
}
n = n + 1;
}
}
}
}
var loadImages = lazyload();
loadImages(); //初始化首页的页面图片
window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);
//函数节流(throttle)与函数去抖(debounce)处理,
//500ms 的延迟,和 1000ms 的间隔,当超过 1000ms 未触发该函数,则立即执行该函数,不然则延迟 500ms 执行该函数
</script>
</body>
</html>
100、for-in 循环会遍历出原型上的属性吗?
1.使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问:
Object.prototype.say="cgl"; // 修改Object.prototype
var person ={ age: 18 };
for (var key in person) {
console.log(key, person[key]);//这里用person.key得不到对象key的值,用person[key] 或者 eval("person."+key);
} //结果: age 18
say cgl
123456
2.只遍历对象自身的属性,而不遍历继承于原型链上的属性,使用hasOwnProperty 方法过滤一下。
Object.prototype.say="cgl";
var person ={
age: 18
};
for (var key in person) {
if(person.hasOwnProperty(key)){
console.log(key, eval("person."+key));
}
}
123456789
二.Object.keys()
Object.keys() 方法会返回一个由给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。返回值是这个对象的所有可枚举属性组成的字符串数组。
Object.prototype.say="cgl";
var person ={ age: 18};
console.log(Object.keys(person));//结果 ["age"]
123
小技巧:object对象没有length属性,可以通过Object.keys(person).length,来获取person的长度了。