JavaScript面试(-------------------------------------------)
this是什么
—大多语言中,’this’代表由类实例化的当前对象。在JavaScript中,’this’通常表示’拥有’方法的对象,但具体取决于一个函数被调用的方式。
方法调用模式
当一个函数被保存为对象的一个属性并调用该方法时,this被绑定至该对象。即使用”obj.method”形式
var val = "outer"; var methodCall = { val: "inner", printVal: function(){ console.log(this.val); } } methodCall.printVal(); //"inner"
构造器调用模式
类似面向对象语言中对象的概念,使用new后,this被绑定至该实例。
var val = "outer"; function methodCall(){ this.val = "inner"; } methodCall.prototype = { printVal: function(){ console.log(this.val); } } var a = new methodCall(); a.printVal(); //"inner"
函数调用模式
函数未作为对象的属性时,其当作一个正常函数来调用,此时this被绑定至全局对象。
var val = "outer"; function methodCall(){ this.val = "inner"; } methodCall.prototype = { printVal: function(){ var that = this; var innerPrintVal = function(){ console.log(that.val); console.log(this.val) } innerPrintVal(); } } var a = new methodCall(); a.printVal();
打印结果为
inner outer
- a.printVal调用printVal的方式为构造器调用模式,这时的this被绑定到a的作用域。将其赋给that,此时that就代表了a的作用域。
- a.printVal调用innerPrintVal的方式为函数调用模式,这时的this被绑定到全局作用域。
注意,在a.printVal中,this是被绑定在a的作用域上的。但是在调用innerPrintVal时,this在该函数中被再一次绑定到全局作用域了。因此在innerPrintVal中调用console.log(this.val)打印的结果为”outer”
apply/call模式
手动指定this的值
var val = "outer"; function printVal(){ console.log(this.val); } function methodCall(){ this.val = "inner"; } methodCall.prototype = { printVal: function(){ console.log(this.val); } } var a = new methodCall(); a.printVal(); //"inner" a.printVal.call(a); //"inner" printVal.call(a); //"inner" printVal(); //"outer" printVal.apply(window); //"outer" a.printVal.apply(window); //"outer"
输出结果为:
inner inner inner outer outer outer
可以看出,无论是采用构造器调用模式的a.printVal或是采用函数调用模式的printVal,this均会被apply/call绑定为手动指定的值
1、查找作用域
当前函数在哪个作用域下定义的,那么他的上级作用域就是谁 , 和函数在哪执行没有任何关系
//作用域实例
var num = 12;
function fn(){
var num = 120;
return function(){
console.log(num);
}
}
var f = fn();
f();//->120
(function(){
var num = 1200;
f();//->120
}())
2、++i 和 i++的区别
//实例说明
var i = 5;
5 + i++ //->10
//i++ 是先运算i本身再加1
//========
var j = 5;
5 + (++j) //->11
//++j 是本身先加1再运算
//面试题
var n = 2;
var num = 5 + (++n) + (n++) + (n++) + (++n); // - > 21
3、预解释
在js执行之前,浏览器首页会把带var和function的关键字进行提前申明或定义;var(只是提前申明) function(提前申明+定义)
注:如果在全局作用域下定义一个 num = 10(没有var)和带var 是有区别的
//实例说明
console.log(num);//->undefined
console.log(num1);//->num1 is not defined
var num = 10;//->先预解释->window.num = 10
num1 = 10;//->window.num1 = 10
//不带var的num1没有提前申明所以报is not definend
预解释面试题
//实例1
function a(b){
alert(b);
function b(){
alert(b);
}
b();
}
a(1); //1->alert(b函数),2->alert(函数)
//实例2
alert(a); //undefined
var a = 0;
alert(a); //0
function fn(){
alert(a); //0;因为没var, 所以这里的a会被看作是全局的,往上查找,找到a=0,所以是0,如果全局也没有就会报错
a = 2;
alert(a); //2
}
fn()
alert(a); //2,fn把这全局的a修改了
//实例3
<script>
alert(a);//报错,因为遇到<script>标签对时,会先对这一块进行预解析,运行到下面的才会再进行预解析,下面没预解析,所以找不到a,于是报错了
</script>
<script>
alert(a); //undefined
var a = 0;
alert(a); //0
</script>
4、区分this
-
函数执行,首先看函数名是否有".",有的话,"."前面是谁this就是谁;没有的话this就是window
-
自执行函数的this永远是window
-
给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this是当前的元素
-
构造函数中的this是这个类的实例
-
this还可以通过call、apply、bind来改变
function fn(){
console.log(this);
}
var obj = {
name:"李四",
writeJs:fn
}
obj.writeJs();//this->obj
var fn = obj.writeJs;
fn();//this->window
function sum() {
fn();//this->window
}
sum();
(function() {
fn();//this->window
)()
document.getElementById('div').onclick = fn;//this->#div
document.getElementById('div').onclick = function() {
//this->#div
fn();//this->window
}
this综合实例(360面试题)
var num = 20;
var obj = {
num: 30,
fn: (function (num) {
this.num *= 3;
num += 15;
var num = 45;
return function () {
this.num *= 4;
num += 20;
console.log(num);
}
}) (num)
}
var fn = obj.fn;
fn();//65
obj.fn();//85
5、单例模式
描述同一个事物(同一个对象)的属性和方法放在一个内存空间下,起到分组的作用,这样不同事物之间的属性名相同,相互也不会发生冲突,我们把这种分组编写代码的模式叫做“单例模式”;
var obj = {
name: '张三',
age: '18',
writeJs: function(){
console.log('my is '+this.name+' can write js');
}
}
obj.writeJs();
注:obj又叫做“命名空间”,单例模式项目开发经常使用,我们可以使用单例模式进行模块化开发;
6、工厂模式
单例模式虽然能解决分组作用,但是不能实现批量生产,属于手工作业模式;
工厂模式->“函数的封装”,“低耦合高内聚”:减少页面中的冗余代码,提高代码的重复利用
function createJs(name,age){
var obj = {};
obj.name = name;
obj.age = age;
obj.writeJs = function(){
console.log('my is '+ this.name +' can write js');
}
return obj;
}
var zhangsan = createJs('张三','18');
zhangsan.writeJs();
所有的编程语言都是面向对象开发的。就有类的继承、封装、多态
-
继承:子类继承父类的属性和方法
-
封装:函授的封装
-
多态:当前方法的多种形态
后台语言中的多态包含重载和重写。js中的多态不存在重载,方法名一样,后面的会把前面的覆盖掉,最后只保留一个方法。(js中有一个类似重载但不是重载:可以根据传递的参数不一样,实现不同的功能)重写:子类重写父类的方法
7、构造函数模式
-
构造函数是通过new关键词创建一个实例;var ex = new CreateJs();其中ex就是CreateJs的实例,生成CreateJs这个类;
-
Js中所有的类都是函数数据类型,它通过new执行变成一个类,但是他本身也是个普通的函数
-
Js中所有的实例都是对象数据类型
-
在构造函数模式中,类中出现的this.xxx=xxx中this是当前类的一个实例
-
不同实例之间方法不一样(下例)
-
在构造函数模式中,浏览器会默认把我们的实例返回(返回对象数据类型的值);如果我们手动写return返回;
-
如果ruturn是一个基本数据类型的值,当前实例不变,例如:return 10;
-
如果return是一个引用数据类型的值,当前实例会被自己返回的值替换,例如:ruturn {name:"张三"}
-
-
检测某个实例是否属于这个类 instanceof;
-
zhangsan instancaof CreateJs->true
-
in:检测某一个属性是否属于这个对象 attr in object,不管是私有属性还是共有属性,只要存在,用in检测都是true
-
hasOwnProperty:用来检测某一个属性是否为这个对象的私有属性,这个方法只能检测私有属性 obj.hasOwnProperty(attr);
-
function CreateJs(name,age) {
this.name = name;
this.age = age;
this.writeJs = function() {
console.log('my is '+ this.name +' can write js');
}
}
var zhangsan = new CreateJs('张三','18');
zhangsan.writeJs();
var lisi = new CreateJs('李四','20');
lisi.writeJs();
zhangsan.writeJs === lisi.writeJs//false
8、原型链模式
-
每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值;
-
prototype上浏览器天生给它加了个属性constructor(构造函数),属性值是当前函数(类)本身;
-
每一对象数据类型(普通对象、实例、prototype..)天生自带一个属性__proto__,属性值是当前实例所属类的prototype(原型)
-
Object是Js中所有对象的基类,Object.prototype上没有__proto__这个属性
function CreateJs(name,age) {
this.name = name;
this.age = age;
}
CreateJs.prototype.writeJs = function() {
console.log('my is '+ this.name +' can write js');
}
var zhangsan = new CreateJs('张三','18');
zhangsan.writeJs();
var lisi = new CreateJs('李四','20');
lisi.writeJs();
zhangsan.writeJs === lisi.writeJs//true
通过对象名.属性名获取属性值的时候,首先在对象的私有属性找,如果私有属性存在,则获取的是私有属性值;
如果私有没有,则通过__proto__找到所属类的原型,原型上存在的话获取的是公有的属性值;
如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.protoype为止
9、call、apply、bind使用
call、apply的区别
对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
实例:
//1、数组之间追加
var array1 = [12 , "foo" , {name "Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
//2、获取数组中的最大值最小值(数组中本身没有max方法)
var numbers = [5, 458 , 120 , -215 ];
var maxInNumbers = Math.max.apply(Math, numbers), //458
maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
//3、检验数据类型
Object.prototype.toString.call(obj) === '[object Array]' ;
//4、类数组转数组
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
bind
bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
//bind是先绑定this和参数,不执行本身函数
//call和apply是绑定this和参数后立即执行本身函数
var func = bar.bind(foo);
func(); // 3
10、计算数组中最大值
<!--1、排序法-->
var arr = [12,43,2323,455,23,5];
arr.sort(function(x,y) {
return x-y;
})
var max = arr[arr.length-1];
<!--2、假设法-->
var arr1 = [123,34,54,23,56,1];
var max = arr1[0];
for(var i = 0;i < arr1.length;i++ ) {
var cur = arr1[i];
if(cur>max) {
max = cur[i];
}
}
<!--3、Math.max + apply法-->
var arr2 = [23,43,123,341,3233];
var max = Math.max.apply(null,arr2);
<!--4、Math.max + eval法-->
var arr3 = [567,23,42,45,1,98];
var max = eval("Math.max("+arrr.toString()+")");
11、数组求平均数
<!--1、类数组转数组-->
function avgFn() {
var arr = Array.prototype.slice.apply(arguments);
arr.sort(function(x,y) {
return x - y;
})
arr.shift()
arr.pop();
var arg =(eval(arr.join("+"))/arr.length).toFixed(2)
return arg;
}
avgFn(68,97,97,91,99.1,89.5,98.23)
<!--2、借助于call-->
function avgFn() {
var arr = [].sort.call(arguments,function(x,y) {
return x-y;
});
[].shift.call(arguments);
[].pop.call(arguments);
return (eval([].join.call(arguments,"+"))/arguments.length).toFixed(2);
}
avgFn(68,97,97,91,99.1,89.5,98.23)
12、sort的深入
arr.sort();只能默认排序10以内的数字和26字母排序
var arr = [1,3,4,65,23,32,43,567];
arr.sort();//log->[1, 23, 3, 32, 4, 43, 567, 65]
var arr1 = [2,5,6,4,9,1.2,0.9];
arr1.sort();//log->[0.9, 1.2, 2, 4, 5, 6, 9]
var arr2 = ['a','y','b','t','p','c'];
arr2.sort();//log->["a", "b", "c", "p", "t", "y"]
arr.sort(function(){})传递参数
<!--升序-->
var arr = [1,3,4,65,23,32,43,567];
arr.sort();
/**
*执行中a和b的值
*a b
*1 3
*3 4
*4 65
*65 23
*4 23
*65 32
*23 32
*65 43
*32 43
*65 567
*a - b > 0 a和b调换位置
*a - b <= 0 位置不变
*/
arr.sort(function(a,b){
return a - b;
})
<!--降序-->
arr.sort(function(a,b){
return b - a;
})
利用localeCompare字母排序
localeCompare第一个字符串字母按照26个字母顺序排序,如果第一个字母相同会按照第二个字母排序以此类推,汉字会先转换成拼音再排序,如果汉字同音会按照汉字unicode顺序排
'a'.localeCompare('b')
//-1
'c'.localeCompare('b')
//1
var arr = [{name:"wangwu",age:17},{name:"lisi",age:17},{name:"dahuang",age:21}];
arr.sort(function(a,b){
return a.name.localeCompare(b.name);
})
//[{name:"dahuang",age:21},{name:"lisi",age:17},{name:"wangwu",age:17}]
var arr = [{name:"小吕",age:17},{name:"老王",age:17},{name:"大黄",age:21}];
arr.sort(function(a,b){
return a.name.localeCompare(b.name);
})
//[{name:"大黄",age:21},{name:"老王",age:17},{name:"小吕",age:17}]
13、DOM回流(重排 reflow)、DOM重绘、DOM映射
-
DOM回流:DOM树渲染完毕以后,只要页面中的HTML结构发生变化(增加删除元素、位置发生变化),浏览器都要重新计算一遍最新的DOM结构,重新对当前页面进行渲染;
-
DOM重绘:DOM树位置不发生变化,如元素的颜色背景发生变化,会只针对这个元素渲染,不渲染整个页面
-
DOM映射:页面中的标签和Js中获取到的元素对象(元素集合)是紧紧绑定在一起的,页面中HTML结构改变了,Js不需要重新获取,集合里面的内容也会跟着自动改变
14、js中数据绑定的方法
1、动态创建节点方式
var oUl = document.getElementById('ul');
var oLi = document.createElement('li');
oLi.innerHTML = "hello world";
oUl.appendChild(oLi);
2、字符串拼接的方式
var oUl = document.getElementById('ul');
var str = '';
for(var i = 0;i < arr.length; i++) {
str += "<li>";
str += "hello" + arr[i];
str += "</li>"
}
oUl.innerHTML += str;
3、文档碎片方式
var oUl = document.getElementById('ul');
var frg = document.createDocumentFragment();//创建一个文档碎片
var oLi = document.createElement('li');
oLi.innerHTML = "hello world";
frg.appendChild(oLi);
oUl.appendChild(frg);
frg = null;//手动清空碎片
15、正则
什么是正则
它是一个规则,用来处理字符串的规则。
正则的创建
1. 字面量创建
var reg = /\d/;
2. 实例创建
var reg = new RegExp("\d");
元字符
每一个正则表达式都是由元字符和 修饰符组成
-
具有特殊意义的元字符
\ :转义后面字符所代表的含义 ^ :以某一个元字符开始 $ :以某一个元字符结束 \n :匹配一个换行符 . :除了\n以外的任意字符 () :分组 x|y :x或y中的一个 [xyz] :x或者y或者z中的任何一个字符 [a-z] :a-z之间的任意字符 [^a-z]:除了a-z之间的任何字符 \d :一个0-9之间的任何数字 \b :匹配一个边界符 \w :数字字母下划线中的任意一个字符 \s:匹配一个空白字符 空格、制表符、换页符...
-
代表出现次数的量词元字符
\* :出现0到多次 \+ :出现1到多次 ? :出现0到1次 {n} :出现n次 {n,} :出现n到多次 {n,m} :出现n到m次
()分组的作用
-
改变x|y的优先级
var reg = /^18|19$/;
//18、19、181、189、119、819、1819....true
var reg = /^(18|19)$/;
//18、19true
-
分组引用
var reg = /^(\w)\1(\w)\2$/;
reg.test("aabb")//true
reg.test("abcd")//false
//\1代表和第一个分组出现一模一样的内容
//\2代表和第二个分组出现一模一样的内容
//去除重复的字符
var str = 'aaaabbbbccccddddddssss440000008888';
str.replace(/(\w)\1+/g,"$1");
//"abcds408"
-
分组捕获
正则在捕获的时候,不仅仅把大正则匹配到,而且还可以把小分组的内容捕获到
var reg = /^(\d{2})(\d{4})(\d{4})(\d{4})\d{2}(\d{1})[\d|X]$/
var arr = reg.exec('340604198802112411');
//["340604198802112411", "34", "0604", "1988", "0211", "1"]
//340604198802112411大正则匹配的内容
//"34", "0604", "1988", "0211", "1"小分组匹配
[](1)、在中括号中出现的所有的字符都是代表本身意义的字符(没有特殊的含义)(2)、中括号不识别两位数
var reg = /^[.]$/;
reg.test('1');//false
reg.test('.');//true
var reg = /[12]/;
//1||2 不是12
var reg = /^[21-57]$/;
//2||1-5||7中的任意一个
//年龄介于[16-58]
var reg = /^(1[6-9]|[2-4]\d|5[0-8])$/;
//简单的验证邮箱
var reg = /^[\w.-]+@[\da-zA-Z]+(\.[a-zA-Z]{2,4}){1,2}$/;
//中国标准真实姓名2-4位汉字
var reg = /^[\u4e00-\u9fa5]{2,4}$/;
//身份证号码
//340604198802112411
//34(省)0604(市区县)19880211(出身年月)24(没用)1(奇数男、偶数女)1(0-9||X)
var reg = /^(\d{2})(\d{4})(\d{4})(\d{4})\d{2}(\d{1})[\d|X]$/;
正则的捕获exec
-
捕获的内容是个数组
var reg = /\d+/;
reg.exec('ducen23niubi21');
//0:"23",index:5,input:"ducen23niubi"
-
正则捕获的特点
懒惰型:每一次执行exec只捕获第一个匹配的内容,在不进行任何处理的情况下,在执行多次捕获,捕获的还是第一个匹配的值
var reg = /\d+/
reg.lastIndex//0
res = reg.exec('ducen23niubi12')//["23"]
reg.lastIndex//0
res = reg.exec('ducen23niubi12')//["23"]
//解决懒惰性,在正则后加修饰符g
var reg = /\d+/g
reg.lastIndex//0
res = reg.exec('ducen23niubi12')//["23"]
reg.lastIndex//7
res = reg.exec('ducen23niubi12')//["12"]
reg.lastIndex//14
res = reg.exec('ducen23niubi12')//null
-
贪婪性:正则每一次捕获都是按照匹配最长的结果
var reg = /\d+/;
res = reg.exec('ducen2017niubi12');
//捕获到的是["2017"]而不是2
//解决正则的贪婪性(在量词元字符后面添加一个?)
var reg = /\d+?/;
res = reg.exec('ducen2017niubi12');
//捕获的是["2"];
++?在正则中有很多作用,放在一个不同的元字符后面代表出现0-1次 /d?/(数字出现一次或不出现);放在一个量词的元字符后面是取消捕获的贪婪性++
正则的修饰符(g、i、m)
var reg = /\d/gim
//g:全局匹配
//i:忽略大小写匹配
//m:多行匹配
var reg = /\d+/g
var arr = []
res = reg.exec('ducen23niubi12');
while(res) {
arr.push(res[0]);
res = reg.exec('ducen23niubi12')
}
arr//["23", "12"]
字符串中的match方法
把所有和正则匹配的字符都获取到
var reg = /\d+/g
var arr = 'ducen23niubi12'.match(reg);
arr//["23", "12"]
虽然在当前的情况下match比我们的exec更加简便一些,但是match中存在一些自己处理不了的问题:在分组捕获的情况下,match只能捕获大正则匹配的内容,而对小正则捕获的内容无法获取
字符串中的replace方法
var str = "ducen2015ducen2016";
str.replace('ducen','huming');
//"huming2015ducen2016"
str.replace(/ducen/g,function(arg){
console.log(arguments);
<!--执行2次
["ducen", 0, "ducen2015ducen2016"]
["ducen", 9, "ducen2015ducen2016"]
-->
return 'huming';
})
//"huming2015huming2016"
str.replace(/ducen(\d+)/g,function(arg){
console.log(arguments)
console.log(arguments[1])//2015 2016 获取小正则
})
<!--["ducen2015", "2015", 0, "ducen2015ducen2016"]-->
<!--["ducen2016", "2016", 9, "ducen2015ducen2016"]-->
replace中的匿名函数执行次数,取决于正则捕获的次数
每一次执行匿名函数里面传递的参数值arguments和我们自己通过exec捕获到的结果是非常类似的(即使正则有分组,我们也可以通过arguments获取)
return:你返回的结果是啥,就相当于把当前大正则捕获的内容替换成返回的内容
var arr = ['零','壹','贰','叄','肆','伍','陆','柒','捌','玖']
var s = "20170401";
s.replace(/\d/g,function(){
return arr[arguments[0]]
})
<!--"贰零壹柒零肆零壹"-->
<!--千位分割符-->
//1
"2343289".replace(/^(\d{1,3})((?:\d{3})+)$/,function(){
return arguments[1]+"," + arguments[2].replace(/(\d){3}(?!$)/g,function(){
<!--(?=$)、(?!$)正向预测、负向预测 -->
return arguments[0]+ ","
})
})
//2
var str = '2343289';
str.replace(/(\d)(?!$)/g,function(res,i){
if((str.length-i-1)%3==0) {
return res+ ","
}else{
return res;
}
})
//3
var str = '2343289';
str = str.split('').reverse().join('');
str = str.replace(/(\d{3})/g,'$1,');
str.split('').reverse().join('');
<!--结果:2,343,289-->
正则中?的用法
-
在量词后面代表0到1次(?)
var reg = /^(\+|-)?\d+(\.\d+)?$/;
-
匹配不捕获(?:)
var reg = /^(?:\+|-)?\d+(\.\d+)?$/
-
取消正则贪婪捕获,把?放在量字后面(?)
var reg = /\d+/;
res = reg.exec('ducen2017niubi12');
//捕获到的是["2017"]而不是2
//解决正则的贪婪性(在量词元字符后面添加一个?)
var reg = /\d+?/;
res = reg.exec('ducen2017niubi12');
//捕获的是["2"];
-
正向预查、负向预查(条件)
(?!n)->匹配任何其后紧接指定字符串 n 的字符串。
(?=n)->匹配任何其后没有紧接指定字符串 n 的字符串。
javascript基础总结(二)——异步编程情况
异步:规定要做一件事,不是立马执行这件事,需要等一定的时间,这样的话,我们不会等着它执行,而是继续执行下面的操作,只有将下面的事情处理完了,才会返回头处理之前的事情;如果下面的事情并没有处理完成,不管之前的事情有没有到时间,都踏踏实实的给我等着;
1、定时器都是异步编程
var n = 0;
setTimeout(function(){
n++;
console.log(n);//2->1
},1000)
console.log(n);//1->0
var n = 0;
setTimeout(function(){
n++;
console.log(n);//2->1
},0)
console.log(n);//1->0
所有的定时器都会放在任务队列池中,时间短的放在前面;
var n = 0;
setTimeout(function(){
n++;
console.log('1:'+n);//2:2
},2000)
setTimeout(function(){
n++;
console.log('2:'+n);//2:1
},1000)
console.log(n);//1->0
如果定时器时间一样会从上向下执行;
var n =0;
setTimeout(function(){
n++;
console.log('1:'+n);
},2000)
var timer= setInterval(function(){
n++;
console.log('2:'+n);
if(n==5){
clearInterval(timer);
}
},1000)
//执行顺序
//2:1
//1:2
//2:3
//2:4
//2:5
2、所有的事件绑定都是异步编程
for 循环执行完成后才会走绑定事件
for(var i = 0;i < oLis[i].length;i++) {
oLis[i].onclick = funciton() {
changeEvent(i);//i永远是最后一个
}
}
3、ajax异步读取数据时
4、通过回调函数实现异步
通过ajax和setTimeout辅助实现
//jquery中的$.get方法等...
$.get('url', function(p) {
//some
});
//借助于setTimeout、setInterval
function f1(callback){
setTimeout(function () {
// f1的任务代码
callback(); // step3
}, 1000);
}
f1();//setep1
console.log('x');//step2
如果该调用需要耗费很多时间,执行队列就会因等待而阻塞,采用回调函数,执行队列继续进行,等到调用结束,通过调用回调函数的方式,通知执行队列,处理执行结果.
javascript基础总结(三)——盒子模型
1、js中的盒子模型
通过js中提供一系列的方法和属性获取页面中元素的样式信息值;
2、client系列
内容的宽高:是给元素定义的width/height这两个样式。如果没有设置height值,容器的高度会根据里面内容自己适应,这样获取的值就是真实的内容的高;如果设置固定的高度,不管内容是多少,内容的高度指的都是设定的这个值;
真实内容的宽高:如果设置的height是200px,如果内容有溢出,那么真实内容的高度是把溢出内容的高度也要加起来;
clientHeight || clientWidth
//内容的宽度/高度+左右/上下填充(padding)
clientLeft || clientTop
//左边框/上边框的高度
3、offset系列
offsetHeight || offsetWidth
//clientHeight/clientWidth + 左右/上下边框(和内容是否溢出没有关系)
offsetParent
//当前元素的父级参照物
offsetLeft || offsetTop
//当前元素的外边框距离父级参照物的内边框的偏移量
计算元素距离body的上部和左部的距离
function offset(el) {
var oLeft = el.offsetLeft,
oTop = el.offsetTop,
oParent = el.offsetParent;
while(oParent) {
//ie8下不计算边框
if (navigator.userAgent.indexOf('MSIE 8.0') === -1) {
oLeft += oParent.clientLeft;
oTop += oParent.clientTop;
}
oLeft += oParent.offsetLeft;
oTop += oParent.offsetTop;
oParent = oParent.offsetParent;
}
return {'left': oLeft,'top': oTop}
}
offset(box2);//{left: 239, top: 218}
注:在标准的IE8浏览器中,我们使用offsetLeft/offsetTop其实是把父级参照物的边框已经算在内,在IE8浏览器下就不需要单独加边框了;
4、scroll系列
scrollHeight || scrollWidth
//真实内容的高度/宽度(包含溢出)+ 左填充/上填充
//注:获取到的结果都是约等于的值,因为同一个浏览器是否设置overflow=hidden对于最终的结果是有影响的;在不同的浏览器中我们获取的结果也是不同的。
scrollTop || scrollLeft
//滚动条卷去的高度
5、操作浏览器本身盒子模型信息
-
clientWidth/clientHeight是当前浏览器可视窗口的宽度和高度
-
scrollWidth/scrollHeight是当前页面的真实宽度和高度(所有屏的高度和宽度的和:是一个约等于值)
-
要兼容浏览器获取浏览器盒子模型信息我们需要这样写
document.documentElement[attr] || document.body[attr];
//documentElement在前body在后
//获取
document.documentElement.clientWidth || document.body.clientWidth;
//设置
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
<!--注:都需要写两个-->
6、获取浏览器盒子模型信息的兼容方法
function win(attr,value) {
//获取值
if(typeof value === "undefined") {
return document.documentElement[attr] || document.body[attr];
}
document.documentElement[attr] = value;
document.body[attr] = value;
}
win('clientWidth');
win('scrollTop',0);
es6中的箭头函数
箭头函数(arrow function)
箭头函数相当于一个匿名函数
x => x * x;
//同等于下面的匿名函数
//x - >传参
//x * x -> return
function(x) {
return x * x;
}
如果箭头函数含表达式就必须加{...} 和 return
x => {
if(x>10) {
return x + x;
}else {
return x * x;
}
}
如果箭头函数不是一个参数,参数就必须加()->(x,y)
//两个参数
(x,y) => x * y
//没有参数
() = > 1 + 4
//可变参数
var fn = (x,...rest) => {
for(var i = 0;i < rest.length; i++) {
x += rest[i]
}
return x;
}
//rest是个Array [4,5]
fn(1,4,5);//10
如果return的值是一个对象需要加()进行区分,防止冲突
var fn = x => { foo: x }
fn(3);//undefined
var fn = x => ({ foo: x })
fn(3);//Object {foo: 3}
箭头函数中的this由上下文决定
//es6 =>写法
var obj = {
age: 18,
fnc : function (x) {
var fn = x => this.age + x;//this->obj
return fn(x);
}
}
obj.fnc(5)//23
//es5写法
//错误的写法
var obj = {
age: 18,
fnc : function (x) {
var fn = function (x) {
return this.age + x;//this->window
}
return fn(x);
}
}
obj.fnc(5)//NaN
//正确的写法
var obj = {
age: 18,
fnc : function (x) {
var that = this;//this->obj
var fn = function (x) {
return that.age + x;
}
return fn(x);
}
}
obj.fnc(5)//23
如果用call()或者apply()调用箭头函数时,无法对this进行绑定(传入的第一个参数被忽略):
var obj = {
age: 18,
fnc : function (x) {
var fn = x => this.age + x;//this->obj
return fn.call({age:20},x);//用call无法改变this的指向,箭头函数中的age依然等于18
}
}
obj.fnc(5)//23