javascript 基础总结(一)——综合

阅读 899
收藏 68
2017-04-26
原文链接:segmentfault.com

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");

元字符

每一个正则表达式都是由元字符和 修饰符组成

  1. 具有特殊意义的元字符

    
    \ :转义后面字符所代表的含义
    
    ^ :以某一个元字符开始
    
    $ :以某一个元字符结束
    
    \n :匹配一个换行符
     
    . :除了\n以外的任意字符
    
    () :分组
    
    x|y :x或y中的一个
    
    [xyz] :x或者y或者z中的任何一个字符
    
    [a-z] :a-z之间的任意字符
    
    [^a-z]:除了a-z之间的任何字符
    
    \d :一个0-9之间的任何数字
    
    \b :匹配一个边界符
    
    \w :数字字母下划线中的任意一个字符
    
    \s:匹配一个空白字符 空格、制表符、换页符...
    
    
  2. 代表出现次数的量词元字符

    \* :出现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 的字符串。
评论