秋招面试准备 JS1

1几个很实用的BOM属性对象方法 参考27、BOM的属性对象方法_y_xiuzhu的博客-CSDN博客

1 BOM的属性对象方法

BOM的对象用于访问浏览器的功能,提供了当前窗口中加载的文档有关的信息,和一些导航功能。

BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是widow

BOM由一些列相关的对象构成,并且每个对象提供很多方法与属性

BOM缺乏标准,javascript语法的标准组织是ECMA,DOM的标准是W3C

BOM最初是Nelscape浏览器标准的一部分

在这里插入图片描述

window对象:表示浏览器窗口,是JS的顶层对象。

location对象:浏览器当前的URL信息

navigator对象:浏览器本身信息

history对象:浏览器的浏览历史记录信息

screen对象:浏览器的屏幕信息。

document对象:代表当前窗口的网页文档。该对象是JS对DOM的具体实现,本部分不谈论其用法。

1 Window对象(核心对象)

window对象表示一个浏览器窗口或一个frame框架,它处于对象层次的最顶端,它提供了处理浏览器窗口的方法和属性。

window对象是浏览器对象中的默认对象,所以可以隐式地引用window对象的属性和方法。在浏览器环境中,添加到window对象中的方法、属性等,其作用域都是全局的。JavaScript中的标准如下,以下两行代码是等价的:

new Date();
//等价于
new window.Date();

2 DOM(document)相关对象

DOM可以认为是BOM的一个子集。DOM中文档操作相关对象,如:Node、Document、Element等DOM节点类型对象,都可以作为window对象的子属性出现

document是window对象的子属性,它是Document对象实例,表示当前窗口文档对象。通过该对象,可以对文档和文档中元素、节点等进行操作

3 frames对象

frames对象是一个集合,表示当前页面中使用的子框架。如果页面中使用了框架,将产生一个框架集合frames,在集合中可以用数字下标(从0开始)或名字索引框架。集合中的每一个对象,包含了框架的页面布局信息,以及每一个框架所对应的window对象。

4navigator对象

navigator是指浏览器对象,该对象提供了当前正在使用的浏览器的信息。navigator对象中的属性是只读的,在W3C在HTML5标准中,对该对象进行了规范。由于浏览器的不同,该对象的具体值可能有所区别。

5 history对象

history对象来保存浏览器历史记录信息,也就是用户访问的页面。浏览器的前进与后退功能本质上就是history的操作。history对象记录了用户浏览过的页面,通过该对象提供的API可以实现与浏览器前进/后退类似的导航功能

6 location对象

location是一个静态对象,该对象是对当前窗口URL地址的解析。该对象提供了可以访问URL中不同部分的信息属性,通过Location对象也可以实现页面或锚点跳转等功能

7 screen对象

screen对象中包含了用户显示器屏幕相关信息。通过该对象,可以访问用户显示器屏幕宽、高、色深等信息。

 2 click在ios上有300ms延迟,原因及如何解决?

方法一、粗暴型:禁用缩放

既然双击缩放是造成300ms延迟的原因,那么只要禁用缩放就可以了,禁用缩放,也就没有了双击产生缩放的操作,那么就不需要等待300ms,也就没有了300ms的延迟

<meta name="viewport" content="width=device-width,user-scalable=no">

方法二、FastClick

原理:在检测到touched事件后,立即触发一个模拟click事件,并把浏览器300ms之后真正触发的click事件阻断掉。

window.addEventListener( "load", function() {
    FastClick.attach( document.body );
}, false );

3 onclick与addEventListener区别  addEventListener与onclick的区别_qq_44473483的博客-CSDN博客

onclick用法

element.onclick = functionRef 

functionRef是一个函数,通常是在别处声明的函数名,或者是一个函数表达式

<ul id = "list>
    <li id = "add_event">red</li>
    <li id = "on_click">yellow</li>
</ul>

 DOM格式如上,JavaScript代码

var on_click = document.getElementById('on_click');
on_click.onclick = function(){
     alert('我是click1');
};
on_click.onclick = function(){
     alert('我是click2');
};

可想而知,只会弹出一个弹出框,虽然绑定了两次,弹出“我是click2”

一个click处理器在同一时间只能指向唯一的对象,因此就算对于一个对象绑定了多次,但是让回出现最后一次的绑定

addEventlistener绑定click事件:

currentTarget.addEventListener(type, listener, option) 

同样上面的DOM结构,对应的JavaScript代码

var addEvent = document.getElementById('add_event');
addEvent.addEventListener('click',function(){
    alert('我是addEvent1');
},false);
addEvent.addEventListener('click',function(){
    alert('我是addEvent2');
},false);

运行结果,两次绑定的事件,都能成功运行,也就是前后弹出我是addEvent1、我是addEvent2,由此可知,对于一个可以绑定的事件对象,想多次绑定事件都能运行,选用addEventListener

总结

1、addEventListener可以对同一个元素绑定多个事件,执行顺序从上到下依次执行。而onclick同一个元素只能绑定一个事件,如有多个,后面的时间会覆盖前面的件。2

document.getElementById("myDIV").addEventListener("click", function() {
  alert(1)
});
document.getElementById("myDIV").addEventListener("click", function() {
  alert(2)
});
//以上代码会先弹出1,在弹出2
document.getElementById("myDIV").onclick = function () {
  alert(1)
};
document.getElementById("myDIV").onclick = function () {
  alert(2)
};
//以上代码只会弹出2。

2 addEventListener的第三个参数为布尔类型,默认值为false,也就是执行冒泡机制,如为true,则执行捕获机制。

 在这里插入图片描述

3 addEventListener它对任何DOM元素都是有效的,而不仅仅对HTML元素有效。

4 注册addEventListener事件时不需要写on,而onclick方式则必须加on

document.getElementById("myDIV").addEventListener("click", myFunction);
document.getElementById("myDIV").onclick = myFunction;

 5 在移除事件上,onclick使用的是指针指向null,例如document.οnclick= null,而addEventListener则使用的是独有的移除方法removeListener要使用此方法,addEventListener必须执行的是外部函数或存在函数名,不然则不能使用)6

// 向 <div> 元素添加事件句柄
document.getElementById("myDIV").addEventListener("mousemove", myFunction);

// 移除 <div> 元素的事件句柄
document.getElementById("myDIV").removeEventListener("mousemove", myFunction);

//如是以下类型的代码,则不能使用removeEventListener
document.getElementById("myDIV").addEventListener("mousemove", function() {});

6 addEventListener为DOM2级事件绑定,onclick为DOM0级事件绑定

7 IE678只能使用attachEvent,无addEventListener 

4get请求传参长度的误区HTTP GET请求传参最大长度的理解误区_进击的攻城狮-zxc的博客-CSDN博客

1、HTTP协议未规定GET和POST的长度限制

2、GET的最大长度显示是因为浏览器和web服务器限制了URI的长度

3、不同的浏览器和WEB服务器,限制的最大长度不一样

4、要支持IE,则最大长度为2083byte,要只支持Chrome,则最大长度8182byte

一、误解

大家都知道http中存在GET和POST这两种最常用的请求方式。(PUT、DELETE不在本文讨论范围之内)

误解:HTTP协议下的GET请求参数长度是有大小限制的,最大不能超过xx,而POST请求时无限制的

1、首先即使有长度限制,也是限制的是整个URI长度,而不仅仅是你的参数值数据长度

2、HTTP协议从未规定GET/POST的请求长度限制是多少

3、所谓的请求长度限制是由浏览器和web服务器决定和设置的,各种浏览器和web服务器的设定均不一样,这依赖于各个浏览器厂家的规定或者可以根据web服务器的处理能力来设定。

5补充get和post请求在缓存方面的区别

get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。POST不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

6说一下闭包闭包,看这一篇就够了——带你看透闭包的本质,百发百中_一只小绵羊-CSDN博客_闭包

1、概念

闭包函数:声明在一个函数中的函数,叫做闭包函数。

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数返回(寿命终结)了之后。

2、特点

让外部访问函数内部变量成为可能

局部变量会常驻在内存中

可以避免使用全局变量,防止全局变量污染

会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

3、闭包的创建

闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。

闭包内存泄漏为:key=value,key被删除了value常驻内存中,局部变量闭包升级版(中间引用的变量)=>自由变量;

4、闭包的应用场景

结论:闭包找到的是同一地址中父级函数中对应变量最终的值

例子1

function funA(){
  var a = 10;  // funA的活动对象之中;
  return function(){   //匿名函数的活动对象;
        alert(a);
  }
}
var b = funA();
b();  //10

例子2

function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   //1 2 3 1 2 3

例子3 

var i = 0;
function outerFn(){
  function innnerFn(){
       i++;
       console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();     //1 2 3 4

例子4

function fn(){
	var a = 3;
	return function(){
		return  ++a;                                     
	}
}
alert(fn()());  //4
alert(fn()());  //4    

例子5

function outerFn(){
var i = 0;
  function innnerFn(){
      i++;
      console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();    //1 1 2 2

例子6

(function() { 
  var m = 0; 
  function getM() { return m; } 
  function seta(val) { m = val; } 
  window.g = getM; 
  window.f = seta; 
})(); 
f(100);
console.info(g());   //100  闭包找到的是同一地址中父级函数中对应变量最终的值

例子7

function a() { 
  var i = 0; 
  function b() { alert(++i); } 
  return b; 
} 
var c = a(); 
c();      //1 
c();      //2 

例子8

function f() { 
  var count = 0; 
  return  function() { 
      count++; 
      console.info(count); 
  } 
} 
var t1 = f();
t1();     //1 
t1();     //2 
t1();     //3 

例子9

var add = function(x) { 
  var sum = 1; 
  var tmp = function(x) { 
      sum = sum + x; 
      return tmp;    
  } 
  tmp.toString = function() { 
      return sum; 
  }
  return tmp; 
} 
alert(add(1)(2)(3));     //6

例子10

var lis = document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++){
  (function(i){
      lis[i].onclick = function(){
           console.log(i);
      };
  })(i);       //事件处理函数中闭包的写法
}  

例子11

function m1(){
     var x = 1;
     return function(){
          console.log(++x);
     }
}
 
m1()();   //2
m1()();   //2
m1()();   //2
 
var m2 = m1();
m2();   //2
m2();   //3
m2();   //4

例子12 

var  fn=(function(){
   var  i=10;
   function  fn(){
      console.log(++i);
   }
   return   fn;
})() 
fn();   //11
fn();   //12

例子13

function love1(){
     var num = 223;
     var me1 = function() {
           console.log(num);
     }
     num++;
     return me1;
}
var loveme1 = love1();
loveme1();   //输出224

例子14

function fun(n,o) {
    console.log(o);
    return {
         fun:function(m) {
               return fun(m,n);
         }
    };
}
var a = fun(0);  //undefined
a.fun(1);  //0  
a.fun(2);  //0  
a.fun(3);  //0  
var b = fun(0).fun(1).fun(2).fun(3);   //undefined  0  1  2
var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);  //undefined  0  1  1

例子15

function fn(){
   var arr = [];
   for(var i = 0;i < 5;i ++){
	 arr[i] = function(){
		 return i;
	 }
   }
   return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
   console.log(list[i]());
}  //5 5 5 5 5

例子16

function fn(){
  var arr = [];
  for(var i = 0;i < 5;i ++){
	arr[i] = (function(i){
		return function (){
			return i;
		};
	})(i);
  }
  return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
  console.log(list[i]());
}  //0 1 2 3 4

5说一下类的创建和继承js类的创建和继承_le__seul的博客-CSDN博客_js类的创建和继承

要了解js类的创建和继承,我们要先掌握js原型与原型链。

1、js原型

JavaScript规定,每一个函数都有一个prototype对象属性,其指向另一个对象(原型链上)。prototype上的所有属性和方法,都会被构造函数的实例继承,我们可以把不变的共用属性和方法直接定义在prototype对象

原型链

实例对象与原型之间的连接,叫做原型链。

__proto__(隐式连接)属性是每一个对象以及函数都隐含的属性。对于每一个含有__proto__属性,他所指向的是创建他的构造函数的prototype。原型链就是通过这个属性构建的。

function Pro() {
  //...
}
var pro = new Pro();

其中Pro中有prototype属性,而pro没有,但是在pro中隐含的__proto__属性指向Pro.prototype。

既有:

pro.__proto__ === Pro.prototype; //true

__proto__属性是每一个对象以及函数都隐含的一个属性,对于每一个含有__proto__属性,他所指向的是新创建他的构造函数的prototype。原型链就是通过这个属性构建的

function A() {
}
function B() {
}
var a1 = new A();
B.prototype = a1;
var b1 = new B();

可以链式的查找:

b1.__proto__ === a1; //true
b1.__proto__.__proto__ === A.prototype; //true
b1.__proto__.__proto__.__proto__ === Object.prototype; //true


2类的创建(es5)

首先new一个function,在这个function的prototype中添加属性和方法。

创建一个Animal类:

//定义一个动物类
function Animal(name) {
	this.name = name;  //属性
	this.sleep = function() {   //实例方法
		console.log(this.name + 'sleep...');
	}
}
//在原型链上添加一个原型方法
Animal.prototype.eat = function(food) {
	console.log(this.name + 'eat...' + food)
}


3.类的继承

原型链继承

原型链继承是JavaScript实现继承的主要方法。利用原型让一个引用类型继承另一个引用类型的属性和方法

//创建一个空对象
function Cat() {
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
var cat = new Cat;

通过控制台可以观察到

在这里插入图片描述

 打印cat对象发现其实继承了animal的属性和方法

特点:基于原型链,既是父类的实例,也是子类的实例

缺点,不能实现多继承

构造继承

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没有用原型)

function Cat() {
    Animal.call(this);
    this.name = name || 'cat';
}
var cat = new Cat();

控制台观察:

在这里插入图片描述

可以看到继承了来自父类的属性(name)和方法(sleep()),但没有继承原型链上定义的方法eat(), 进一步验证:

在这里插入图片描述

特点:可以实现多继承

缺点:只能实现继承父类的属性和方法,不能继承原型链上的属性和方法

组合继承:

相当于构造继承和原型链继承的组合体,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat() {
    Animal.call(this);
    this.name = name || 'cat';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();

 控制台输出,通过对比前面介绍的原型链继承的控制台输出,可以明显的发现其调用了两份父类构造函数。

在这里插入图片描述

 缺点:调用了两次父类构造函数,生成了两份实例

寄生组合继承

可以说是组合继承的优化,通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例

function Cat() {
    Animal.call(this);
    this.name = name || 'cat';
}
(function() {
    //创建一个空对象
    var Super = function() {};
    //将实例作为子类的原型
    Super.prototype = Animal.prototype;
    Cat.prototype = new Super();
}) ()
var cat = new Cat();

控制台输出:

在这里插入图片描述

6前端中的事件流 前端中的事件流_牛客博客 (nowcoder.net)

前端中的事件流

JavaScript与HTML之间的交互是通过事件实现的,而用户与浏览器页面的互动是通过事件来实现的。

事件流

当页面中放了一个按钮,你用鼠标点击这个按钮,虽然你只是点击了这个按钮,但其实你也同时点击了这个按钮的所有父级元素。

事件流描述的是从页面中接收事件的顺序

DOM事件流

DOM2级事件规定事件流包含三个阶段:

事件捕获阶段

处于目标阶段

事件冒泡阶段

事件冒泡

即事件从下级传递给上级。如果同时给儿子和父亲定义了事件,那么点击儿子,触发儿子的事件之后,也会再触发父亲的事件。浏览器是默认事件冒泡行为的

但是在开发的过程中,我们大多数情况下不需要事件冒泡,我们想要的是点击儿子事件就只触发儿子事件,而不需要将父亲的事件触发。所以阻止上一级事件的触发就叫做阻止事件冒泡。

阻止事件冒泡

event.cancelBubble = true;

event.stopPropagation();

事件监听

addEventListener:addEventListener是从DOM2级事件新增的指定事件处理程序的操作,这个方法接收三个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

addEventListener()方法,用于向指定元素添加事件句柄,它可以更简单的控制事件,语法为:

element.addEventListener(event, function, useCapture);

第一个参数是事件的类型(如"click" 或"mousedown").

第二个参数是事件触发后调用的函数。

第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。

事件传递有两种方式,冒泡和捕获

事件传递定义了元素事件触发的顺序,如果你将P 元素插入到div 元素中,用户点击P元素,在冒泡中,内部元素先被触发,然后再触发外部元素;捕获中,外部元素先被触发,再触发内部元素。

https://zhuanlan.zhihu.com/p/114276880

对JS事件流的深入理解

在JS事件循环中,我们解除了很多JS自己触发的事件。但是当我们在网页上进行某些类型的交互时,也会触发事件,比如在某些内容上的点击,鼠标经过某个特定元素或按下键盘上的某些按键。当一个节点产生一个事件时,该事件会在元素节点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程称为DOM事件流。

什么是事件

JavaScript和HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。可以使用监听器(或事件处理程序)来预定事件,以便事件发生时执行相应的代码。通俗的说,这种模型其实就是一个观察者模式。(事件是对象主题,而这一个个的监听器就是一个个观察者)

什么是事件流

事件流描述的就是从页面中接收事件的顺序。而早期的IE和Netscape提出了完全相反的事件流概念,IE事件流是事件冒泡,而Netscape的事件流就是事件捕获。

事件冒泡事件捕获

IE提出的事件流是事件冒泡,即从下至上,从目标触发的元素逐级向上传播,直到window对象。

而Netscape的事件流就是事件捕获,即从document逐级向下传播到目标元素。由于IE低版本浏览器不支持,所以很少使用事件捕获。

后来ECMAScript在DOM2中对事件流进行了进一步规范,基本上就是上述二者的结合

DOM2级事件规定的事件流包括三个阶段:(1)事件捕获阶段(2)处于目标阶段(3)事件冒泡阶段

DOM事件处理

DOM节点中有了事件,那我们就需要对事件进行处理,而DOM事件处理分为四个级别:DOM0级事件处理,DOM0级事件处理,DOM2级事件处理和DOM3级事件处理。

其中DOM1级事件处理标准中并没有定义相关的内容,所以没有所谓的DOM1级事件处理;DOM3级事件在DOM2级事件的基础上添加了更多的事件类型。

DOM0

DOM0级事件具有极好的跨浏览器优势,会以最快的速度绑定。第一种方式是内联模式(行内绑定),将函数名直接作为html标签中属性的属性值。

<div onclick="btnClick()">click</div>
<script>
function btnClick(){
    console.log("hello");
}
</script>

内联模型的缺点就是不符合W3C中关于内容与行为分离的基本规范。第二种方式是脚本模型(动态绑定),通过在JS中选中某个节点,然后给节点添加onclick属性。

<div id="btn">点击</div>
<script>
var btn=document.getElementById("btn");
btn.onclick=function(){
    console.log("hello");
}
</script>

点击输出hello,没有问题;如果我们给元素添加两个事件

<div id="btn">点击</div>
<script>
var btn=document.getElementById("btn");
btn.onclick=function(){
    console.log("hello");
}
btn.onclick=function(){
    console.log("hello again");
}
</script>

这时候只能输出hello again,很明显,第一个事件函数被第二个事件函数给覆盖掉,所以脚本模型的缺点是同一个节点只能添加一次同类型事件。让我们把div扩展到3个

<div id="btn3">
    btn3
    <div id="btn2">
        btn2
        <div id="btn1">
            btn1
        </div>
    </div>
</div>
<script>
    let btn1 = document.getElementById("btn1");
    let btn2 = document.getElementById("btn2");
    let btn3 = document.getElementById("btn3");
    btn1.onclick=function(){
        console.log(1)
    }
    btn2.onclick=function(){
        console.log(2)
    }
    btn3.onclick=function(){
        console.log(3)
    }
</script>

当我们点击btn3的时候输出3,那么当我们点击btn1的时候呢?

我们发现最先触发的是最底层btn1的事件,最后才是顶层btn3的事件,因此很明显是事件冒泡。DOM0级只支持冒泡阶段。

DOM2

进一步规范之后,有了DOM2级事件处理程序,其中定义了两个方法:

1、addEventListener()--添加事件侦听器

2、removeEventListener()--删除事件侦听器

函数均有3个参数,第一个参数是要处理的事件名,第二个参数是作为事件处理程序的函数 第三个参数是一个Boolean值,默认false表示使用冒泡机制,true表示捕获机制。

<div id="btn">点击</div>

<script>
var btn=document.getElementById("btn");
btn.addEventListener("click",hello,false);
btn.addEventListener("click",helloagain,false);
function hello(){
    console.log("hello");
}
function helloagain(){
    console.log("hello again");
}
</script>

这时候两个事件处理程序都能够成功触发,说明可以绑定多个事件处理程序,但是注意,如果,如果定义了一模一样的监听方法,是会发生覆盖的,即同样的事件和事件流机制下相同方法只会触发一次,

<div id="btn">点击</div>

<script>
var btn=document.getElementById("btn");
btn.addEventListener("click",hello,false);
btn.addEventListener("click",hello,false);
function hello(){
    console.log("hello");
}
</script>

这时候hello只会执行一次,把div扩展到3个

<div id="btn3">
    btn3
    <div id="btn2">
        btn2
        <div id="btn1">
            btn1
        </div>
    </div>
</div>
<script>
    let btn1 = document.getElementById('btn1');
    let btn2 = document.getElementById('btn2');
    let btn3 = document.getElementById('btn3');
    btn1.addEventListener('click',function(){
        console.log(1)
    }, true)
    btn2.addEventListener('click',function(){
        console.log(2)
    }, true)
    btn3.addEventListener('click',function(){
        console.log(3)
    }, true)
</script>

这时候看到顺序和DOM0中的顺序反过来了,最外层的btn最先触发,因为addEventListener最后一个参数是true,捕获阶段进行处理。

那么冒泡和捕获阶段谁先执行呢?我们给每个元素分别绑定了冒泡和捕获两个事件

btn1.addEventListener('click',function(){
    console.log('btn1捕获')
}, true)
btn1.addEventListener('click',function(){
    console.log('btn1冒泡')
}, false)

btn2.addEventListener('click',function(){
    console.log('btn2捕获')
}, true)
btn2.addEventListener('click',function(){
    console.log('btn2冒泡')
}, false)

btn3.addEventListener('click',function(){
    console.log('btn3捕获')
}, true)
btn3.addEventListener('click',function(){
    console.log('btn2冒泡')
}, false)

我们看到先执行捕获阶段的处理程序,后执行冒泡阶段的处理程序,我们把顺序换一下再看运行结果:

btn1.addEventListener('click',function(){
    console.log('btn1冒泡')
}, false)
btn1.addEventListener('click',function(){
    console.log('btn1捕获')
}, true)

btn2.addEventListener('click',function(){
    console.log('btn2冒泡')
}, false)
btn2.addEventListener('click',function(){
    console.log('btn2捕获')
}, true)

btn3.addEventListener('click',function(){
    console.log('btn3冒泡')
}, false)
btn3.addEventListener('click',function(){
    console.log('btn3捕获')
}, true)

我们发现在触发的目标元素上不区分冒泡还是捕获,按绑定的顺序来执行。

阻止冒泡

有时候我们需要点击事件不再继续向上冒泡,我们在btn2上加上stopPropagation函数,阻止程序冒泡 

btn1.addEventListener('click',function(){
    console.log('btn1冒泡')
}, false)
btn1.addEventListener('click',function(){
    console.log('btn1捕获')
}, true)

btn2.addEventListener('click',function(){
    console.log('btn2冒泡')
}, false)
btn2.addEventListener('click',function(ev){
    ev.stopPropagation();
    console.log('btn2捕获')
}, true)

btn3.addEventListener('click',function(){
    console.log('btn3冒泡')
}, false)
btn3.addEventListener('click',function(e){
    console.log('btn3捕获')
}, true)

可以看到btn2捕获阶段执行后不再继续往下执行

事件委托

如果有多个DOM节点需要监听事件的情况下,给每个DOM绑定监听函数,会极大的应用页面的性能,因为我们通过事件委托来进行优化,事件委托利用的就是冒泡的原理

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
<script>
    var li_list = document.getElementsByTagName('li')
    for(let index = 0;index<li_list.length;index++){
        li_list[index].addEventListener('click', function(ev){
            console.log(ev.currentTarget.innerHTML)
        })
    }
</script>

正常情况我们给每一个li都会绑定一个事件,但是如果这时候li是动态渲染的,数据又特别大的时候,每次渲染后(有新增的情况)我们还需要重新来绑定,又繁琐又耗性能;这时候我们可以将绑定事件委托到li的父级元素,即ul

var ul_dom = document.getElementsByTagName('ul')
ul_dom[0].addEventListener('click', function(ev){  
    console.log(ev.target.innerHTML)
})

上面代码中我们使用了两种获取目标元素的方式,target和currentTarget,那么他们有什么区别呢:

target返回触发事件的元素,不一定是绑定事件的元素

currentTarget返回的是绑定事件的元素

事件委托的优点:

提高性能:每一个函数都会占用内存空间,只需添加一个事件处理程序代理所有事件,所占用的内存空间更少

2动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以一样具有和其他元素一样的事件。

7如何让事件先冒泡后捕获事件模型:事件委托、代理?如何让事件先冒泡后捕获_炙@年的博客-CSDN博客

事件委托

又叫事件代理,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

原理:

事件冒泡机制,从最深的节点开始,然后逐步向上传播事件。

作用:

①支持为同一个DOM元素注册多个同类型事件;

②可将事件分为事件捕获和事件冒泡

addEventListener(event,function,useCapture布尔值)
默认为false冒泡,true为捕获
attachEvent() 
//IE8及IE更早版本
 detachEvent() 
//移除事件监听
//不使用事件捕获
window.onload = function(){
    	let oBox = document.getElementById("box");
    	oBox.onclick = function(){
    		alert(1);   //不触发
    }
    	oBox.onclick = function(){
    		alert(2);   //触发
    	}
}
 
//使用事件捕获
window.onload = function(){
    oBox.addEventListener("click",function(){
    	alert(1);   //触发
    })
	oBox.addEventListener("click",function(){
	alert(2);   //触发
	})
}

事件捕获:

当一个事件触发后,从window对象触发,不断经过下级节点后,直到目标节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点,都会触发对应事件

当为事件捕获(useCapture:true)时,先执行body的事件,再执行div的事件

件冒泡
当事件到达目标节点后,会沿着捕获阶段的路线原路返回。同样,所有经过的节点,都会触发对应的事件

当为事件冒泡(useCapture:false)时,先执行div的事件,再执行body的事件

先冒泡后捕获

根据w3c标准,应先捕获后冒泡,若要实现先冒泡后捕获,给一个元素绑定两个addEventListener,其中一个第三个参数设置为false(即冒泡),另一个第三个参数设置为true(即捕获),调整它们的代码顺序,将设置为false的监听事件放在设置为true的监听事件前面即可。

8说一下事件委托JavaScript 事件委托详解 - 知乎 (zhihu.com)

基本概念

事件委托,通俗地讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素;一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;

在这里,取快递就是一个事件,每个同学指的是需要响应事件的DOM元素,而统一出去领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该分配到被代理元素中的哪一个或者哪几个。

事件冒泡

前面提到DOM中事件委托的实现是利用事件冒泡的机制,那么事件冒泡是什么呢?

在document.addEventListener的时候我们可以设置事件模型:事件冒泡、事件捕获,一般来说都是事件冒泡的模型。

如上图所示,事件模型是指分为三个阶段:

捕获阶段:在事件冒泡的模型中,捕获阶段不会响应任何事件;

目标阶段:目标阶段就是值事件响应到触发事件的最底层元素上。

冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层(根节点),事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;###事件

委托的优点

1、减少内存消耗

试想一下,如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

如果给每一个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能

因此比较好的方法就是把这个点击事件绑定到他的父层,也就是ul上,然后在执行事件的时候再去匹配判断目标元素;

所以事件委托可以减少大量的内存消耗,节约效率

2,动态绑定事件

比如上诉的例子中列表项就几个,我们给每个列表都绑定了事件;

在很多时候,我们需要通过AJAX或者用户操作动态的增加或者去删除列表项元素,那么在每一个改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数饿过程中取匹配的;

所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的

jQuery中的事件委托

jQuery中的事件委托主要这几种方法来实现:

$.on:基本用法:$('.parent').on('click', 'a', function () { console.log('click event on tag a'); }),它是.parent元素之下的a元素的事件代理到$('.parent')之上,只要在这个元素上有点击事件,就会自动寻找到.parent元素下的a元素,然后响应事件;

$.delegate: 基本用法: $('.parent').delegate('a', 'click', function () { console.log('click event on tag a'); }),同上,并且还有相对应的 $.delegate 来删除代理的事件;

$.live: 基本使用方法: $('a', $('.parent')).live('click', function () { console.log('click event on tag a'); }),同上,然而如果没有传入父层元素 $(.parent),那事件会默认委托到 $(document) 上;(已废除)

实现功能

基本实现

比如我们有这样一个HTML片段:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

我们来实现把#list下的li元素的事件代理委托到它的父层元素也就是#list上;

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

在上诉代码中,target元素则是在#list元素之下具体被点击的元素,然后通过判断target的一些属性,比如:nodeName,id等等,可以更精确地匹配到某一类#list li元素之上;

使用Element.matches精确匹配

如果改变下 HTML 成:

<ul id="list">
  <li className="class-1">item 1</li>
  <li>item 2</li>
  <li className="class-1">item 3</li>
  ......
  <li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li

这里,我们想把#list元素下的li元素(并且它的class为class-1)的点击事件委托代理到#list之上;

如果通过上诉的方法我们还需要在 `if (target.nodeName.toLocaleLowerCase === 'li')` 判断之中在加入一个判断 `target.nodeName.className === 'class-1'`;

但是如果想像 CSS 选择其般做更加灵活的匹配的话,上面的判断未免就太多了,并且很难做到灵活性,这里可以使用 Element.matches API 来匹配;

Element.matches API的基本使用方法: Element.matches(selectorString),selectorString 既是 CSS 那样的选择器规则,比如本例中可以使用 target.matches('li.class-1'),他会返回一个布尔值,如果 target 元素是标签 li 并且它的类是 class-1 ,那么就会返回 true,否则返回 false;

当然它的兼容性还有一些问题,需要 IE9 及以上的现代化浏览器版本;

我们可以使用 Polyfill 来解决兼容性上的问题:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}

加上 Element.matches 之后就可以来实现我们的需求了:

if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  if (target.matches('li.class-1')) {
    console.log('the content is: ', target.innerHTML);
  }
});

9说一下图片的懒加载和预加载懒加载和预加载 - 简书 (jianshu.com)

1、懒加载

1什么是懒加载?

懒加载也就是延迟加载

当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需要请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片真正的路径,让图片显示出来。这就是图片懒加载。

2为什么要使用懒加载?

很多页面,内容很丰富,页面很长,图片加多。比如各种商城页面。这些页面图片数量多,而且比较大,少说百来k,多则上兆。要是页面载入就一次性加载完毕,估计大家都会等到黄花变成黄花菜了。

3懒加载的原理是什么?

页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过JavaScript设置了图片路径,浏览器才会发送请求。

懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把真正的路径才咋元素的“data-url”(这个名字起个自己认识好记的就行)属性里,需要的时候提取出来,再设置。

4懒加载的实现步骤

①首先,不要将图片地址放在src属性中,而是放在其他属性(data-original)中

②页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值提取出来放到src属性中

③在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值去除存放到src属性中。

5懒加载的优点是什么?

页面加载速度快,可以减轻服务器的压力,节约了流量,用户体验好

2、预加载

1什么是预加载?

提前加载图片,当用户需要查看时直接从本地缓存中渲染

2为什么使用预加载

图片预加载到浏览器,访问者可以顺利的在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验

3实现预加载的方法有哪些

方法一:用CSS和JavaScript实现预加载
方法二:仅使用JavaScript实现预加载
方法三:使用Ajax实现预加载

详见:http://web.jobbole.com/86785/

3、懒加载和预加载的对比

1)概念:

懒加载也叫延迟加载,JS图片延迟加载,延迟加载图片后符合某些条件时才加载某些图片

预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染

2)区别

两种技术的本质:两种的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力

3)懒加载的意义及实现方式有:
意义:
懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
实现方式:
1.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。

4)预加载的意义及实现方式有:
意义:
预加载可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反映。
实现方式:
实现预载的方法非常多,比如:用CSS和JavaScript实现预加载;仅使用JavaScript实现预加载;使用Ajax实现预加载。
常用的是new Image();设置其src来实现预载,再使用onload方法回调预载完成事件。只要浏览器把图片下载到本地,同样的src就会使用缓存,这是最基本也是最实用的预载方法。当Image下载完图片头后,会得到宽和高,因此可以在预载前得到图片的大小(方法是用记时器轮循宽高变化)。

10mouseover和mouseenter的区别mouseover和mouseenter的区别 - 嗯嗯呢 - 博客园 (cnblogs.com)

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡过程。对应的事件是mouseout

mouseenter:当鼠标移入元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的事件是mouseleave

mouseover和mouseenter的异同体现在两个方面:

1. 是否支持冒泡

2.事件的触发时机

先看一张图,对这两件事有一个简单直观的感受。

mouseenter事件的情况:

  当鼠标从元素的边界之外移入元素的边界之内时,事件被触发。而鼠标本身在元素边界内时,要触发该事件,必须先将鼠标移出元素边界外,再次移入才能触发。

也就是说:mouseover支持事件冒泡,而mouseenter不支持事件冒泡

由于mouseenter不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其mouseover和mouseout事件,但不会触发mouseenter和mouseleave事件。

 可见mouseover事件因其具有冒泡的性质,在子元素内移动的时候,频繁被触发,如果我们不希望如此,可以使用mouseenter事件代替之,但是早期只有ie浏览器支持该事件,虽然现在大多数高级浏览器都支持了mouseenter事件,但是难免会有些兼容问题,所以如果可以自己手动模拟,那就太好了。

总结

当事件对象没有子元素时,两者没有区别

不论鼠标指针穿过被选元素其子元素,都会触发 mouseover 事件。对应mouseout

只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件。对应mouseleave

11JS的new操作符做了哪些事情js的new操作符到底做了什么? - 知乎 (zhihu.com)

做了什么?

1、创建了一个空的js对象(即{})

2、将空对象的原型prototype指向构造函数的原型

3、将空对象作为构造函数的上下文(改变this指向)

4、对构造函数有返回值的判断

怎么实现?

/*
  create函数要接受不定量的参数,第一个参数是构造函数(也就是new操作符的目标函数),其余参数被构造函数使用。
  new Create() 是一种js语法糖。我们可以用函数调用的方式模拟实现
*/
function create(Con,...args){
    //1、创建一个空的对象
    let obj = {}; // let obj = Object.create({});
    //2、将空对象的原型prototype指向构造函数的原型
    Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
    //3、改变构造函数的上下文(this),并将剩余的参数传入
    let result = Con.apply(obj,args);
    //4、在构造函数有返回值的情况进行判断
    return result instanceof Object?result:obj;
}

构造函数返回值判断

一般情况下构造函数没有返回值,但是作为函数,是可以有返回值的

那么在构造函数有返回值的情况下,new操作符做了什么?

先看两个例子:

注意一下上面两个返回值的差异

function Person(name){
  this.name = name;
  return 1; // return undefined/NaN/'string'/null
}
let me = new Person('快乐每一天');
console.log(me); // { name:'快乐每一天' }
function Person(name){
  this.name = name;
  return { age:12 };
}
let me = new Person('快乐每一天');
console.log(me); // { age:12 }

结论

在new的时候,会对构造函数的返回值做一些判断

1、如果返回值是基础数据类型,则忽略返回值

2、如果返回值是引用数据类型,则使用return的返回,也就是new操作符无效

12 改变函数内部this指针的指向函数(bind,apply,call的区别)call、apply和bind方法的用法以及区别 - 简书 (jianshu.com)

call、apply、bind的作用是改变函数运行时this的指向,所以先说清楚this。

以下是函数的调用方法:

方法调用模式:

当一个函数被保存为对象的一个方法时,如果调用表达式包含一个提取属性的动作,那么它就是被当做一个方法来调用,此时的this被绑定到这个对象。

    var a = 1
    var obj1 = {
      a:2,
      fn:function(){
        console.log(this.a)
      }
    }
    obj1.fn()//2    

此时的this是指obj1这个对象,obj1.fn()实际上是obj1.fn.call(obj1),事实上谁调用这个函数,this就是谁,补充一下,DOM对象绑定事件也属于方法调用模式,因此它绑定的this就是事件源DOM对象。如:

document.addEventListener('click', function(e){
    console.log(this);
    setTimeout(function(){
        console.log(this);
    }, 200);
}, false); 

点击页面,依次输出:document和window对象

解析:点击页面监听click事件属于方法调用,this指向事件源DOM对象,即obj.fn.apply(obj),setTimeout内的函数属于回调函数,那么可以理解,f1.call(null,f2),所以this指向window。

函数调用模式

就是普通函数的调用,此时的this被绑定到window

最普通的函数调用

function fn1(){
      console.log(this)//window
    }
fn1()

函数嵌套

function fn1(){
    function fn2(){
        console.log(this)//window
    }
    fn2()
}
fn1()

把函数赋值之后再调用‘

var a = 1
var obj1 = {
    a:2,
    fn:function(){
        console.log(this.a)
    }
}
var fn1 = obj1.fn
fn1()//1

obj1.fn()是一个函数function(){console.log(this.a)},此时fn1就是不带任何修饰的函数调用,function(){console.log(this.a)}.call(undefined),按理说打印出来的this应该就是undefined了,但是浏览器有一条规则:

如果你传的context就是null或者undefined,那么window对象就是默认的context(严格模式下默认context是undefined)

因此上面的this绑定的就是window,它也被称为隐性绑定

如果你希望打印出2,可以修改fn1()fn1.call(obj1),显示地绑定this为obj1

回调函数

var a = 1
function f1(fn){
    fn()
    console.log(a)//1
}
f1(f2)

function f2(){
    var a = 2
}

改写代码如下:

var a = 1
function f1(){
    (function (){var a = 2})()
    console.log(a)//1
}
f1()

仍旧是最普通的函数调用,f1.call(undefined),this指向window,打印出来的是全局的a。

借此,我们终于可以解释为什么setTimeout总是丢失this了,因为它也就是一个回调函数而已。

setTimeout(function() {
    console.log(this)//window
    function fn(){
        console.log(this)//window
    }
    fn()
}, 0);

构造器调用模式

new一个函数时,背地里会创建一个连接到prototype成员的新对象,同时this会被绑定到那个新对象上。

function Person(name,age){
// 这里的this都指向实例
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}

var dot = new Person('Dot',2)
dot.sayAge()//2

call

call方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window

var arr = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])//89

可以这么理解:

obj1.fn() 
obj1.fn.call(obj1);

fn1()
fn1.call(null)

f1(f2)
f1.call(null,f2)

看一个例子:

var obj = {
    message: 'My name is: '
}

function getName(firstName, lastName) {
    console.log(this.message + firstName + ' ' + lastName)
}

getName.call(obj, 'Dot', 'Dolby')

apply

apply接收两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined时,默认指向window

var arr = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])//89

可以这么理解:

obj1.fn() 
obj1.fn.call(obj1);

fn1()
fn1.call(null)

f1(f2)
f1.call(null,f2)

事实上apply和call的用法几乎相同,唯一的差别在于:当函数需要传递多个变量的时候,apply可以接受一个数组作为参数输入,call则是接受一系列的单独变量

看一个例子:

var obj = {
    message: 'My name is: '
}

function getName(firstName, lastName) {
    console.log(this.message + firstName + ' ' + lastName)
}

getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby

可以看到,obj是作为函数上下文的对象,函数getName中this指向了obj这个对象。参数firstName和lastName是放在数组中传入getName函数。

call和apply可用来借用别的对象的方法,这里以call()为例:

var Person1  = function () {
    this.name = 'Dot';
}
var Person2 = function () {
    this.getname = function () {
        console.log(this.name);
    }
    Person1.call(this);
}
var person = new Person2();
person.getname();       // Dot

从上面我们看到,Person2 实例化出来的对象 person 通过 getname 方法拿到了 Person1 中的 name。因为在 Person2 中,Person1.call(this) 的作用就是使用 Person1 对象代替 this 对象,那么 Person2 就有了 Person1 中的所有属性和方法了,相当于 Person2 继承了 Person1 的属性和方法。

对于什么时候该用什么方法,其实不用纠结。如果你的参数本来就存在一个数组中,那自然就用 apply,如果参数比较散乱相互之间没什么关联,就用 call。像上面的找一组数中最大值的例子,当然是用apply合理。

bind

和call很相似,第一个参数是this的指向,从第二个参数开始时接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用

bind返回值是函数

var obj = {
    name: 'Dot'
}

function printName() {
    console.log(this.name)
}

var dot = printName.bind(obj)
console.log(dot) // function () { … }
dot()  // Dot

bind方法不会立即执行,而是返回一个改变了上下文this后的函数。而原函数printName中的this并没有被改变,依旧指向全局对象window

参数的使用

function fn(a, b, c) {
    console.log(a, b, c);
}
var fn1 = fn.bind(null, 'Dot');

fn('A', 'B', 'C');            // A B C
fn1('A', 'B', 'C');           // Dot A B
fn1('B', 'C');                // Dot B C
fn.call(null, 'Dot');      // Dot undefined undefined

call是把第二个及以后的参数作为fn方法的实参传进去,而fn1方法的实参则是在bind中参数的基础上再往后排

总结

call、apply和bind函数存在的区别:

bind返回对应函数,便于稍后调用,apply,call则是立即调用

除此外,在ES6的箭头函数下,call和apply将失效,对于箭头函数来说:


13JS的各种位置,比如clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别?

  • 箭头函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;所以不需要类似于var _this = this这种丑陋的写法
  • 箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误
  • 箭头函数不可以使用 arguments 对象,,该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替
  • 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数,什么是Generator函数可自行查阅资料,推荐阅读阮一峰Generator 函数的含义与用法Generator 函数的异步应用
clientHeight和offsetHeight属性和元素的滚动、位置没有关系它代表元素的高度,其中:

clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。

图片描述

offsetHeight:包括padding、border、水平滚动条、但不包括margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。

图片描述

接下来讨论出现有滚动条的情况:

当本元素的子元素比本元素高且overflow=scroll时,本元素会scroll,这时:

scrollHeight:因为子元素比父元素高,父元素不想被子元素撑得一样高就显示出了滚动条,在滚动的过程中本元素有部分被隐藏了,scrollHeight代表包括当前不可见部分的元素的高度,而可见部分的高度其实就是clientHeight,也就是scrollHeight>=clientHeight恒成立。在有滚动条时讨论scrollHeight才有意义,在没有滚动条时scrollHeight==clientHeight恒成立。单位px,只读元素

图片描述

scrollTop:代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时scrollTop==0恒成立。单位px,可读可设置

图片描述

offsetTop:当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系,单位px,只读元素

图片描述

14js拖拽功能的实现js拖拽功能的实现_前端校招面试题目合集_牛客网 (nowcoder.com)

首先是三个事件,分别是mousedown,mousemove,mouseup

当鼠标点击按下的时候,需要一个tag标识此时已经按下,可以执行mousemove里面的具体方法

clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用offsetX和offsetY来表示元素的元素的初始位置,移动的距离应该是:

鼠标移动时候的坐标-鼠标按下去时候的坐标

也就是说定位信息为:

鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offsetLeft

还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位下的left以及top等值

15异步加载js的方法JS异步加载的三种方法_阿珊和她的猫的博客-CSDN博客

JavaScript默认是同步加载(又称阻塞模式),这种模式在加载js文件时,会影响后续页面的渲染,一旦网速不好,整个页面将会等待js文件的加载,从而不进行后续页面的渲染,这也是倡导<script>标签放在</body>标签之前的原因

另外,有些js文件是按需加载的,用的时候加载,不用时不必加载。所以引入了异步加载模式(非阻塞模式),即浏览器在下载执行js文件时,会同时进行后续页面的处理

1、defer---以前适用于IE,现在使用于所有主流浏览器

defer属性规定是否对脚本执行进行延迟,直到页面加载为止。defer属性的值只有defer一个,即 defer = ‘defer’,可直接写defer

用法:在script标签里加入defer属性即可,适用于所有scrip脚本

<script src='http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js' defer></script>

添加defer属性后,js脚本在所有元素加载完成后执行,而且是按照js脚本声明的顺序执行

2、async----h5新属性

async属性规定一旦脚本可用,则会异步执行

async的用法和defer一样,但async只适用于外部引用的脚本,即script有src属性时才可使用

<script src='http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js' async></script>

不同的是,添加async属性后,js脚本是乱序执行的,不管你声明的顺序如何,只要某个js脚本加载完就立即执行

3、动态生成scrip标签

在js里创建scrip标签。插入DOM中,加载完成后callback

function loadScript(url, callback){
    var s = document.createElement('script');
    s.type = 'text/javascript';
    if(s.readyState){
        s.onreadystatechange = function(){  //兼容IE
            if(s.readyState == 'complete' || s.readyState == 'loaded'){
                callback();
            }
        }
    }else{
        s.onload = function(){  //safari chrome opera firefox
            callback();
        }
    }
 
    s.src = url;
    document.head.appendChild(s);
}

这样所有的js脚本都会在onload事件后才加载,onload事件会在所有文件内容(包括文本、图片、CSS文件等)加载完成后才开始执行,极大的优化了网页的加载速度,提高了用户体验

16AJAX解决浏览器缓存问题Ajax 解决浏览器的缓存问题 - 闲敲棋子 - 博客园 (cnblogs.com)

ajax能提高页面载入的速度主要的原因是通过ajax减少了重复数据的载入,也就是说在载入数据的同时将数据缓存到内存中,一旦数据被加载其中,只要我们没有刷新页面,这些数据就会一直被缓存在内存中,当我们提交的URL与历史的URL一致时,就不需要提交给服务器,也就是不需要从服务器上面去获取数据。

虽然这样降低了服务器的负载提高了用户的体验,但是我们不能获取最新的数据,为了保证我们读取的信息都是最新的,我们就需要进制他的缓存功能。

解决方案有如下几种:

1、在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。

2、在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。

3、在URL后面加上一个随机数: "fresh=" + Math.random();。

4、在URL后面加上时间戳:"nowtime=" + new Date().getTime();。

5、如果是使用jQuery,直接这样就可以了$.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。

17js的节流和防抖详谈js防抖和节流 - 知乎 (zhihu.com)

首先举一个例子:

模拟在输入框输入后做ajax查询请求,没有加入防抖和节流的效果,这里附上完整可执行代码:

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <title>没有防抖</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = function () {
            //模拟ajax请求
            function ajax(content) {
                console.log('ajax request ' + content)
            }
            let inputNormal = document.getElementById('normal');
​
            inputNormal.addEventListener('keyup', function (e) {
                ajax(e.target.value)
            })
        }
    </script>
</head>
​
<body>
    <div>
        1.没有防抖的输入:
        <input type="text" name="normal" id="normal">
    </div>
</body>
​
</html>

效果:在输入框里输入一个,就会触发一次ajax请求(此处是console)。

没有防抖和节流

缺点:浪费请求资源,可以加入防抖和节流来优化一下

防抖和节流都是为了解决短时间内大量触发某函数而导致的性能问题,比如触发频率过高导致的响应速度跟不上触发频率,出现延迟、假死或卡顿现象。

1防抖(debounce)

1.1什么是防抖

在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时

1.2 应用场景

(1)用户在输入框连续输入一串字符后,只会在输入完后执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源;

(2)window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时触发对应事件,防抖让其只触发一次;

1.3实现

还是上诉例子,这里加入防抖来优化一下,完整代码如下:

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <title>加入防抖</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = function () {
            //模拟ajax请求
            function ajax(content) {
                console.log('ajax request ' + content)
            }
            function debounce(fun, delay) {
                return function (args) {
                    //获取函数的作用域和变量
                    let that = this
                    let _args = args
                    //每次事件被触发,都会清除当前的timeer,然后重写设置超时调用
                    clearTimeout(fun.id)
                    fun.id = setTimeout(function () {
                        fun.call(that, _args)
                    }, delay)
                }
            }
            let inputDebounce = document.getElementById('debounce')
            let debounceAjax = debounce(ajax, 500)
            inputDebounce.addEventListener('keyup', function (e) {
                debounceAjax(e.target.value)
            })
        }
    </script>
</head>
​
<body>
    <div>
        2.加入防抖后的输入:
        <input type="text" name="debounce" id="debounce">
    </div>
</body>
​
</html>

代码说明:

1每一次事件被触发,都会清除当前的timer然后重新设置超时调用,即重新计时。这就会导致每一次高频事件都会取消前一次的超时调用,导致事件处理程序不能被触发;

2只有当高频事件停止,最后一次事件触发的超时调用才能在delay时间后执行;

效果:

加入防抖后,当持续在输入框输入时,并不会发送请求,只有当在指定时间间隔内没有再输入时,才会发送请求。如果先停止输入,但是在指定间隔内又输入,则重新触发计时

2节流(throttle)

2.1什么是节流

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

2.2应用场景

1鼠标连续不断地触发某事件(如点击),只在单位事件内只触发一次

2在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次ajax请求,而不是在用户停下滚动页面操作时采取请求数据

3监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

2.3实现

还是上述列子,这里加入节流来优化一下,完整代码如下:

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <title>加入节流</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = function () {
            //模拟ajax请求
            function ajax(content) {
                console.log('ajax request ' + content)
            }
​
            function throttle(fun, delay) {
                let last, deferTimer
                return function (args) {
                    let that = this;
                    let _args = arguments;
​
                    let now = +new Date();
                    if (last && now < last + delay) {
                        clearTimeout(deferTimer);
                        deferTimer = setTimeout(function () {
                            last = now;
                            fun.apply(that, _args);
                        }, delay)
                    } else {
                        last = now;
                        fun.apply(that, _args);
                    }
                }
            }
            let throttleAjax = throttle(ajax, 1000)
            let inputThrottle = document.getElementById('throttle')
            inputThrottle.addEventListener('keyup', function (e) {
                throttleAjax(e.target.value)
            })
        }
    </script>
</head>
​
<body>
    <div>
        3.加入节流后的输入:
        <input type="text" name="throttle" id="throttle">
    </div>
</body>
​
</html>

效果:实验可发现在持续输入时,会安装代码中的设定,每1秒执行一次ajax请求

3小结

总结下防抖和节流的区别:

-- 效果:

函数防抖是某一段时间内只执行一次;而函数节流是间隔时间执行,不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

-- 原理:

防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 然后重新设置超时调用,即重新计时。这样一来,只有最后一次操作能被触发。

节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。

18JS中的垃圾回收机制Javascript基础:垃圾回收机制_imagine_tion的博客-CSDN博客_js垃圾回收

在这里插入图片描述

 一、什么是内存泄漏?

程序的运行需要内存,当程序提出要求,操作需要就会供给内存,对于不再用到的内存,没有及时释放,就叫做内存泄漏

对于持续进行的服务进程,必须要及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则导致进行崩溃

二、什么是垃圾回收机制

JavaScript具有垃圾收集器,垃圾收集器会按照固定的时间间隔周期性的执行。

最常见的垃圾回收方式有两种:标记清除、引用计数

1、标记清除

原理:这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。

垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。

2、引用计数法

原理:跟踪记录每个值被引用的次数。

另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减1,当这个值的引用次数为0的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为0的这些值。

三、如何观察内存泄漏?

如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

可以浏览器查看内存占用。
通过命令行,命令行可以使用 Node process.memoryUsage提供的方法。process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。(判断内存泄漏,以heapUsed字段为准。

19eval是做什么的JavaScript中eval 是做什么用的?_zhuhai__yizhi的专栏-CSDN博客

eval()函数,这个函数可以把一个字符串当做一个JavaScript表达式一样去执行它

举个小例子:

var the_unevaled_answer = "2 + 3";
var the_evaled_answer = eval("2 + 3");
alert("the un-evaled answer is " + the_unevaled_answer + " and the evaled answer is " + the_evaled_answer);

如果你运行这段eval程序, 你将会看到在JavaScript里字符串"2 + 3"实际上被执行了。
所以当你把the_evaled_answer的值设成 eval("2 + 3")时, JavaScript将会明白并把2和3的和返回给the_evaled_answer。 

20如何理解前端模块化如何理解前端模块化_前端校招面试题目合集_牛客网 (nowcoder.com)

前端模块化就是复杂的文件编程一个一个独立的模块,比如js文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了commonJS规范,AMD,CMD规范等等,以及用于js打包(编译等处理)的工具webpack

21说一下Commonjs、AMD和CMD前端模块化——彻底搞懂AMD、CMD、ESM和CommonJS - 奔跑的瓜牛 - 博客园 (cnblogs.com)

22对象深度克隆的简单实现JS对象深度克隆实现_诗渊的博客-CSDN博客_js深度克隆

首先,我们将JavaScript中的数据分为两大类:原始类型和对象类型。(1)原始类型包括:数值、字符串、布尔值、null、undefined(我们需要克隆的主要是前面三个)(2)对象类型包括:对象(object),函数(Function)、数组(Array)。在克隆过程中对这两类数据类型的处理方式是不一样的。

一、原始类型克隆

1、数值的克隆

var x=1;
var y=x;
y=2;
console.log(x);  //1
console.log(y);  //2

2字符串的克隆

var x="abc";
var y=x;
y="def";
console.log(x);  //abc
console.log(y);  //def

3布尔值的克隆

var x = true;
var y = x;
y=false;
console.log(x);  //true
console.log(y);  //false

由于原始类型存储的是对象的实际数据,因此我们可以通过简单的复制即可得到正确的结果,而且不影响之前的对象。

二、对象类型克隆

1、数组的克隆

var x = [1,2,3];
var y = x;
console.log(y);  //[1,2,3]
y.push(4);
console.log(y);  //[1,2,3,4]
console.log(x);  //[1,2,3,4]

对象类型存储的是对象的引用地址,而把对象的实际内容单独存放,因为对象类型通常比较庞大,这是数据开销和内存开销优化的手段。因此我们不能像原始数据类型一样简单的赋值,而应该应该遍历数据中的每一个元素,将其中的每一个原始数据类型复制过去,做法如下:

var x = [1,2,3];
var y = [];
for (var i = 0; i < x.length; i++) {
    y[i]=x[i];
}
console.log(y);  //[1,2,3]
y.push(4);
console.log(y);  //[1,2,3,4]
console.log(x);  //[1,2,3]

2对象的克隆

参照数组的克隆,我们采用同样的思想进行对象的克隆:

var x = {a:1,b:2};
var y = {};
for(var i in x){
    y[i] = x[i];
}
console.log(y);  //Object {a: 1, b: 2}
y.c = 3;
console.log(y);  //Object {a: 1, b: 2, c: 3}
console.log(x);  //Object {a: 1, b: 2}

3函数的克隆

var x = function(){console.log(1);};
var y = x;
y = function(){console.log(2);};
x();  //1
y();  //2

由于函数对象克隆之后的对象会单独复制一次并存储实际数据,因此并不会影响克隆之前的对象。所以采用简单的复制“=”即可完成克隆

22将原生的ajax封装成promise

我们为什么要用promise封装ajax呢?是因为promise的then解决了回调地域的问题,所以咱们需要封装一下这样就可以在vue或是react里面使用了。

var  myNewAjax=function(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('get',url);
xhr.send(data);
xhr.onreadystatechange=function(){
if(xhr.status==200&&readyState==4){
var json=JSON.parse(xhr.responseText);
resolve(json)
}else if(xhr.readyState==4&&xhr.status!=200){
reject('error');
}
}
})
}

23js监听对象属性的改变js监听对象属性的改变 - ashen1999 - 博客园 (cnblogs.com)

在ES5中,通过defineProperty()进行监听

  • 假设对user对象的name属性进行监听,当设置name属性值时,会执行相应的函数
Object.defineProperty(user, 'name', {
    set : funtion(value){
               name = value;
                console.log('set: name:' + value)
        }
})

当需要设置对象中多个属性时,使用defineProperties()进行监听

Object.defineProperties(obj,{
  a : {
        configurable: true, // 设置属性可以更改,默认为false
        set : function(value){}
  },      
   b : {
       configurable: true, // 设置属性可以更改,默认为false
        set : function(value){}
  }
} 
})

在ES6中,通过Proxy实现

funtion handle(){
   // 改写set方法,监听设置
    set: funtion(){},
    get: funtion(){}
}

let p = new Proxy({},handle) // 第一个参数为监听的对象,第二个参数为改写的方法

24如何实现一个私有变量,用getName方法可以访问,不能直接访问

1通过defineProperty来实现

obj={
name:yuxiaoliang,
getName:function(){
return this.name
}
}
object.defineProperty(obj,"name",{

2通过函数的创建形式

function product(){
var name='yuxiaoliang';
this.getName=function(){
return name;
}
}
var obj=new product();

25==和===以及Object.is的区别JS中==、===和Object.is()的区别 - sulinlin - 博客园 (cnblogs.com)

1、==:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较;

null==undefined

" "==0  //true

"0"==0  //true

" " !="0" //true

123=="123" //true

null==undefined //true

2、===:恒等,严格比较运算符,不做类型转换,类型不同就是不等;

3、Object.is()是ES6新增的用来比较两个值是否相等的方法,与===的行为基本一致

4、1先说===,这个比较简单,只需要利用下面的规则来判断两个值是否恒等就行了:

①如果类型不同,就不相等

②如果两个值都是数值,并且是同一个值,那么相等。值的注意的是,如果两个值中至少有一个是NaN,那么不相等(判断一个值是否是NaN,用isNaN()或Object.is()来判断

③如果两个都是字符串,每个位置的字符都一样,那么相等;否则不相等、

④如果两个值都引用同一个对象或函数,那么相等,即两个对象的物理地址也必须保持一致,否则不相等

⑤如果两个值都是同样的Boolean值,那么相等

2再说Object.is(),其行为与===基本一致,不过有两处不同:1、+0不等于-0.

2NaN等于自身

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

26setTimeout、setInterval和requestAnimationFrame之间的区别setTimeout、setInterval 和 requestAnimationFrame 之间的区别_DreamFive-CSDN博客

setTimeout():在指定的时间后执行一段代码,只执行一次

setInterval():以固定的时间间隔,重复运行一段代码,可执行多次

与setTimeout、setInterval不同,requestAnimationFrame不需要设置时间间隔

计时器一直是JavaScript动画的核心技术,而编写动画循环的核心是要知道延迟时间多长合适,一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑顺畅,另一方面循环的时间要足够长,这样才能保证浏览器有能力渲染产生的变化。

大多数电脑显示器的刷新频率是60Hz,大概相当于每秒重绘60次。大多数浏览器都会对重绘操作加以限制,不会超出显示器的重绘频率

而setTimeout和setInterval的缺点是他们都不够精确,他们内在的运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中等待执行的时间,如果队列中已经加入了其他任务,那么动画的执行要等前面的任务结束之后才会执行。

requestAnimationFrame采用的是系统时间间隔,保证了最佳绘制效率,不会因间隔时间过短,造成过度绘制,增加开销,也不会因时间间隔太长,造成动画卡顿,它能够让各种网页动画有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。、

特点:requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
在隐藏或不可见的元素中,requsetAnimationFrame 将不会进行重绘或回流,这就意味着更少的 CPU、GPU 和内存使用量。
requestAnimationFrame是浏览器专门提供的 api,在运行时浏览器会自动优化方法的调用,并且页面不是激活状态下,动画会暂停执行,有效节省 CPU 开销。


27实现一个两列等高布局,讲讲思路两列等高布局的实现_depers15的博客-CSDN博客

使用padding和margin属性

28JS怎么控制一次加载一张图片,加载完后再加载下一张.js控制一次加载一张图片,加载完后再加载下一张 - Mahmu_d(مەھمۇد) - 博客园 (cnblogs.com)

29代码的执行顺序从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue · Issue #5 · forthealllight/blog · GitHub

30如何实现sleep效果JS——如何实现sleep的效果(es5或者es6)_qq812457115的博客-CSDN博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值