Spiga

前端提升1:JS

2025-03-01 16:56:47

一、JS数据结构

js的数据定义与检查

var str = 'abc';
var num = 123;
var bool = true;
var und = undefined;
var n = null;
var arr=['x','y','z'];
var obj = {};
var fun = function() {};
console.log(typeof str);   //string
console.log(typeof num);   //number
console.log(typeof bool);  //boolean
console.log(typeof und);   //undefined
console.log(typeof n);     //object
console.log(typeof arr);   //object
console.log(typeof obj);   //object
console.log(typeof fun);   //function

1. 字符串

window.onload = function () {
    // 字符串常用的属性和方法
    // charAt() 方法可返回指定位置的字符。
    // concat() 方法用于连接两个或多个字符串。
    // indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
    // lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置
    // includes() 方法用于判断字符串是否包含指定的子字符串
    // replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串
    // split() 方法用于把一个字符串分割成字符串数组
    // substr() 方法可在字符串中抽取从开始下标开始的指定数目的字符
    // substring() 方法用于提取字符串中介于两个指定下标之间的字符
    // slice(start, end) 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分
    // toLowerCase() 方法用于把字符串转换为小写
    // toUpperCase() 方法用于把字符串转换为大写
    // trim() 方法用于删除字符串的头尾空格

    //1、查找位置
    // charAt() 方法可返回指定位置的字符。
    // indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
    // lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置
    // includes() 方法用于判断字符串是否包含指定的子字符串

    //2、截取
    // slice(start, end) 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分
    // substr() 方法可在字符串中抽取从开始下标开始的指定数目的字符
    // substring() 方法用于提取字符串中介于两个指定下标之间的字符
    var str = 'hello world';
    str.slice(3,-4);//str.slice(3,7)
    str.substr(1,4);//
    str.substring(3,-4) //str.substring(0,3)

    //3、其它
    // split() 方法用于把一个字符串分割成字符串数组
    // replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串
    // toLowerCase() 方法用于把字符串转换为小写
    // toUpperCase() 方法用于把字符串转换为大写
    // trim() 方法用于删除字符串的头尾空格
}

2. 数值

window.onload = function () {
    var num = 100;
	var num2 = 99.9;
    // 数值常用的属性和方法 
	// toFixed() 返回字符串值,它包含了指定位数小数的数字
	// toPrecision() 返回字符串值,它包含了指定长度的数字
	// toString(x)使用x为基数,把数字转换为字符串
    
    // 把变量转换为数值
    // Number() 
    // parseInt() 
    // parseFloat() 
    // PS:这些方法并非数字方法,而是全局 JavaScript 方法
    
    // 向上,向下,四舍五入取整
    // var n = 9.01234;
    // Math.ceil(n);
    // Math.floor(n);
    // Math.round(n);
}

3. 数组

window.onload = function () {
    // 数组常用的属性和方法
    // 属性:
    // 数组长度 length
    // 方法:
    // toString() 把数组转换为数组值(逗号分隔)的字符串。
    // join() 方法也可将所有数组元素结合为一个字符串
    // unshift() 方法(在开头)向数组添加新元素 
    // push() 方法(在数组结尾处)向数组添加一个新的元素
    // pop() 方法从数组中删除最后一个元素
    // shift() 方法会删除首个数组元素,并把所有其他元素“位移”到更低的索引
    // splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那
    // 些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组
    // slice()返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素
    // concat() 方法用于连接两个或多个数组
    // sort ()排序
    // reverse() 方法用于颠倒数组中元素的顺序
    // for循环遍历
    // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。
    // every()对数组的每一项都运行给定的函数,每一项都返回 ture,则返回 true
    // some()方法用于检测数组中的元素是否满足指定条件,如果有一个元素满足条件,则表达式返回true , 剩
    // 余的元素不会再执行检测,
    // 如果没有满足条件的元素,则返回false。
    // filter()对数组的每一项都运行给定的函数,返回 结果为 ture 的项组成的数组
    // map()方法通过对每个数组元素执行函数来创建新数组
    // reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

    //1、添加删除
    // unshift() 方法(在开头)向数组添加新元素 
    // push() 方法(在数组结尾处)向数组添加一个新的元素
    // pop() 方法从数组中删除最后一个元素
    // shift() 方法会删除首个数组元素,并把所有其他元素“位移”到更低的索引
    // splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那
    // 些被删除的元素。如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组
    // slice()返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素

    //2、类型转换
    // toString() 把数组转换为数组值(逗号分隔)的字符串。
    // join() 方法也可将所有数组元素结合为一个字符串

    //3、循环
    // for循环遍历
    // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。
    // map()方法通过对每个数组元素执行函数来创建新数组
    var arr = ['x','y','z'];
    var newArr = arr.map(function(item,index){
        return item+1
    })
    console.log(newArr);
    console.log(arr)

    var arr2 = [{id:1,v:'x1'},{id:2,v:'x2'},{id:3,v:'x3'}];
    //[1,2,3] 或[{id:1},{id:2},{id:3}]
    // var arr3 =[];
    // for(var i=0;i<arr2.length;i++){
    //     arr3.push(arr2[i].id)
    // };
    // console.log(arr3)

    //或
    var arr4 = arr2.map(function(item){
        return{id:item.id,abc:item.v}
    })
    console.log(arr4)


    //4、计算
    // every()对数组的每一项都运行给定的函数,每一项都返回 ture,则返回 true
    // some()方法用于检测数组中的元素是否满足指定条件,如果有一个元素满足条件,则表达式返回true , 剩
    // 余的元素不会再执行检测,
    // 如果没有满足条件的元素,则返回false。
    // filter()对数组的每一项都运行给定的函数,返回 结果为 ture 的项组成的数组
    // reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
    // concat() 方法用于连接两个或多个数组
    // sort ()排序
    // reverse() 方法用于颠倒数组中元素的顺序

    //当前元素  索引
    var f = [19,20,18,40].filter(function(item,index){
        return item>=19
    });
    console.log(f)//[ 19, 20, 40 ]

    var arr2 = [{id:1,check:true},{id:2,check:false},{id:3,check:false},{id:4,check:true}];
    arr2.filter(item=>item.check);
    //模糊匹配
    var arr3 = ['red','blue','green','orange','yellow'];
    var input = 'r';
    var filterColor = arr3.filter(function(v){
        //includes() 方法用于判断字符串是否包含指定的子字符串
        return v.includes(input)
    });
    console.log(filterColor) //[ 'red', 'green', 'orange' ]

    // every()对数组的每一项都运行给定的函数,每一项都返回 ture,则返回 true
    // some()方法用于检测数组中的元素是否满足指定条件,如果有一个元素满足条件,则表达式返回true , 剩
    // 余的元素不会再执行检测
    var x1 = [19,20,55,40].every(function(item,index){
        return item>=19
    });
    console.log(x1) //true   全选

    var x2 = [19,20,55,40].some(function(item,index){
        return item>=100
    });
    console.log(x2) //false  

    // reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
    var arr4 = [1,2,3,4,5]; //累加
    // var n = 0;
    // for (var i=0;i<arr4.length;i++){
    //     n+=arr4[i]
    // };
    // console.log(n) //15

    var x4 = arr4.reduce(function(total,cur){
        return total+cur
    },10);
    // var x4 = arr4.reduce((total,cur)=>total+cur,0);
    console.log(x4); //25


    // concat() 方法用于连接两个或多个数组
    // sort ()排序
    // reverse() 方法用于颠倒数组中元素的顺序
    var arr5 = [1,2,3];
    var arr6 = [4,5,6];
    //var arr7 = arr5.concat(arr6);
    var arr7 = [...arr5,...arr6] 
    console.log(arr7) //[ 1, 2, 3, 4, 5, 6 ]

    var arr8 = ['red','blue','green'];
    console.log(arr8.sort());  //[ 'blue', 'green', 'red' ]

    var arr9 = [3,1,7,5,10];
    console.log(arr9.sort()); //[ 1, 10, 3, 5, 7 ]

    var arr8 = ['green','red','blue'];
    //按字符串长短
    arr8.sort((a,b)=>a.length-b.length);
    console.log(arr8) //[ 'red', 'blue', 'green' ]
}

4. 对象

window.onload = function () {
    // 对象常用的属性和方法
    // JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串;
    // JSON.parse() 方法用于将一个 JSON 字符串转换为对象。
    // hasOwnProperty() 该方法可以判断对象的自有属性是否存在
    // assign() 该方法主要用于对象的合并
    // defineProperties()直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
    // keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
    // values();返回一个给定对象自己的所有可枚举属性值的数组
    // entries();返回一个给定对象自身可枚举属性的键值对数组

    var o1 = {
        id:1,
        name:'ff'
    };
    // o1['id'] //
    // var a = 'id';
    // o1[a]   //变量
    //.和[]的区别  .只能是自身的属性 []即可以是自身的属性也可以是变量

    // hasOwnProperty() 该方法可以判断对象的自有属性是否存在
    console.log(o1.hasOwnProperty('id'))  //true

    function Test(){};
    Test.prototype.id = 100;

    var t = new Test();
    t.value = 'test';
    console.log(t.hasOwnProperty('id')) //判断对象的自有属性是否存在
    console.log('id' in t); //true  in 运算符 检测属性是否存在某个对象中,针对自有属性和继承属性都返回true

    //// assign() 该方法主要用于对象的合并
    var o2 = {id:1};
    var o3 = {v:100};
    var o4 = Object.assign(o2,o3);
    //var o4 = {...o2,...o3}
    console.log(o4); //{ id: 1, v: 100 }

    //类型转换
    // keys() 返回一个由一个给定对象的自身可枚举属性组成的数组
    // values();返回一个给定对象自己的所有可枚举属性值的数组
    // entries();返回一个给定对象自身可枚举属性的键值对数组
    var o5 = { id: 1, v: 100 };
    console.log(Object.keys(o5)) //[ 'id', 'v' ]
    console.log(Object.values(o5)) //[ 1, 100 ]
    console.log(Object.entries(o5)) //[ [ 'id', 1 ], [ 'v', 100 ] ]

    //循环 for in
    var o5 = { id: 1, v: 100 };
    for(var key in o5){
        console.log(key) //id v
        console.log(o5[key]) //1 100
    }

    // defineProperties()直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
    var o6 = {};
    // Object.defineProperty(o6,'id',{
    //     value:10,
    //     writable:false  //属性是否可以修改  true可修改  false只读
    // })
    // console.log(o6);
    // o6.id = 99;
    // console.log(o6.id);

    // Object.defineProperties(o6,{
    //     'id':{
    //         value:10,
    //         writable:false
    //     },
    //     'name':{
    //         value:'ff',
    //         writable:true
    //     }
    // })
    // console.log(o6);
    // o6.id = 99;
    // console.log(o6.id);

    var obj = {};
    var initvalue = 0;
    Object.defineProperty(obj,'id',{
        get:function(){
            return value;
        },
        set:function(value){
            initvalue = value;
        }
    })
    obj.id = 100;
    obj.id
}

二、基本类型与引用类型

1. 内存分配

  • 栈分配:javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存 储在栈中,每种类型的数据占用的内存空间的大小是确定的

    栈由系统自动分配, 例如,声明在函数中一个局部变量var a; 系统自动在栈中为a开辟空间 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

    var a = 10;
    var b = a;
        b = 20;
    console.log(a); // 10
    

    b获取值是a值的一份拷贝,虽然,两个变量的值是相等,但是两个变量保存两不同的基本数据类型值。 b只是保存了a复制的一个副本。所以,当b的值改变时,a的值依然是10

  • 堆分配:javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function)

    它们是通过拷贝和new出来的,这样的数据存储于堆中

    var obj1 = {};
    var obj2 = obj1;
    obj2.name = "obj2";
    console.log(obj1.name); // obj2
    

2. 深拷贝与浅拷贝

window.onload = function () {
    //深拷贝,浅拷贝
    var a = 10;
    var b = a;
    //当a发生变化时,b是否会随着a的变化而变化?
    //变化了   浅拷贝
    //不变     深拷贝

    //深拷贝
    var x = 10;
    var y = x;
    x = 100;
    console.log(y);  //10
    y = 88;
    console.log(x); //100

    //浅拷贝
    var arr = [1, 2, 3];
    var arr2 = arr;
    arr[1] = 100;
    console.log(arr2);  //[ 1, 100, 3 ]
    arr2[1] = 888;
    console.log(arr); //[ 1, 888, 3 ]

    var o1 = { id: 10 };
    var o2 = o1;
    o2.id = 999;
    console.log(o1); //{ id: 999 }

    //是不是引用类型只能是浅拷贝了呢?
    //如何实现引用类型深拷贝
    var arr1 = [1, 2, 3];
    //var arr2 = arr1;  //浅拷贝
    var arr2 = arr1.concat();
    arr1.push(5);
    console.log(arr1); //[1,2,3,5]
    console.log(arr2); //[1,2,3]

    //arr1.concat();仍然还是浅拷贝
    var arr1 = [1, 2, 3, [4, 5]];
    var arr2 = arr1.concat();
    arr1.push(11);
    arr1[3][0] = 100;
    console.log(arr1); //[ 1, 2, 3, [ 100, 5 ], 11 ]
    console.log(arr2); //[ 1, 2, 3, [ 100, 5 ] ]



    var o1 = { id: 1 };
    var o2 = Object.assign({}, o1);
    o1.id = 100;
    console.log(o1.id); //100
    console.log(o2.id); //1

    //多级数据结构
    var o1 = { id: 1, test: 'o1', children: { v: 'o1' } };
    var o2 = Object.assign({}, o1);
    o1.id = 100;
    o2.children.v = 'o2';
    console.log(o1);  //{ id: 100, test: 'o1', children: { v: 'o2' } }
    console.log(o2); //{ id: 1, test: 'o1', children: { v: 'o2' } }

    var obj = {
        name: 'fangfang',
        age: 18,
        children:{id:1}
    }
    var obj2 = new Object();
    obj2.name = obj.name;
    obj2.age = obj.age;
    obj2.children = obj.children;
    obj.name = 'alice';
    obj.children.id = 99;
    console.log(obj); //{ name: 'alice', age: 18, children: { id: 99 } }
    console.log(obj2); //{ name: 'fangfang', age: 18, children: { id: 99 } }



    //深拷贝
    var obj = {
        name: 'fangfang',
        age: 18,
        children:{id:1},
        action:function(){
            console.log('action')
        }
    }
    var obj2 =  JSON.parse(JSON.stringify(obj));
    obj.name = 'alice';
    obj2.children.id = 99;
    console.log(obj);  
    console.log(obj2);

    //完美实现深拷贝:引用类型实现深拷贝
    //自己封装
    //封装的思路:需要递归整个对象树
    var obj = {
        name: 'fangfang',
        age: 18,
        member:[1,2,3,4,5],
        children:{id:1},
        action:function(){
            console.log('action')
        }
    }
    // function deep(){

    // };
    //传参可以是数组,也可以是对象
    //deep({},obj) //第一个参数新对象,第二个参数数据结构
    //deep([],arr) 


    //扩展——遍历obj,将obj的数据赋值给newObj
    function deep(newObj,obj){
        var o = newObj;
        //遍历obj,将obj的数据赋值给newObj
        for(var key in obj){
            o[key] = obj[key]
        };
        return o;
    };
    var o1 = deep({},obj);
    o1.name = 'o1';
    o1.children.id = 888;
    console.log(o1)

    //扩展——如何去满足于多级
    function deep(newObj,obj){
        var o = newObj;
        //遍历obj,将obj的数据赋值给newObj
        for(var key in obj){
            //添加子级判断,是不是引用类型
            if(typeof obj[key] ==='object'){ //typeof针对数组或对象值都是'object'
                //判断obj[key]的类型,是数组还是object对象
                //constructor判断是被谁构造出来的
                o[key] = (obj[key].constructor===Array)?[]:{};
                //递归
                deep(o[key],obj[key]);
            }else {
                o[key] = obj[key];
            }
        };
        return o;
    };
    var arr = [1,2,[3,4,[5,6]]];
    var arr1 = deep([],arr);
    arr1[0] = 10;
    arr1[2][0] = 20;
    arr1[2][2][0] = 30;
    console.log(arr);
    console.log(arr1);
}

三、预解析

定义:可以理解为把变量或函数预先解析到它们被使用的环境中

解析过程

  1. 会预先解析关键字var、function等

  2. 提前赋值

    • var a = 10 提前解析 var a;(此时a的值为undefined
    • 函数,在正式运行代码前,赋值为整个函数块
    console.log(fn)   //function变量提升   function fn(){console.log("123")}
    function fn(){
       console.log("123")
    }
    
  3. 预解析结束后,浏览器再逐行解读代码

    //看看下面的代码输出结果
    console.log(a) 
    var a = 1;
    //解析过程
    var a;
    console.log(a); //var变量提升   undefined
    a = 1;
    

4. 解析原则:预解析过程中,当变量和函数同名时:只留下函数的值,不管谁前谁后,所以函数优先级更高

console.log(fn)   //function变量提升   function fn(){console.log("123")}
var fn = 234;
function fn(){
	console.log("123")
}

window.onload = function () {
    //预解析(提前解析,走后门,变态解析,关系户)
    //预解析对象:var     function 

    //例如
    console.log(a);
    var a = 100;
    //解析过程
    var a;  //预解析   提前声明变量a
    //预解析完毕后,逐行正常由上而下解析
    console.log(a);  //undefined 
    a = 100;

    //function
    //函数声明和函数表达式的区别
    fn(); //OK
    function fn() {  ////函数声明
        return 123
    };
    fn2(); //error
    var fn2 = function () { //函数表达式

    };

    //变量和函数同名时,优先留下函数的值
    console.log(f);
    function f() {
        console.log(456)
    };
    var f = 123;
    //预解析
    function f() {
        console.log(456)
    };
    var f;
    //正常解析
    console.log(f); //function
    f = 123;

    //扩展
    var a = 100;
    function a() {
        console.log(200)
    };
    console.log(a);
    //解析过程
    //预解析
    var a;
    function a() {
        console.log(200)
    };
    //正常解析
    a = 100;
    console.log(a);

    //扩展题
    console.log(a)
    var a = 1;
    function a() { console.log(2) }
    console.log(a)
    var a = 3;
    console.log(a)
    function a() { console.log(4) }
    console.log(a)

    //解析过程
    //预解析
    var a;
    function a() { console.log(2) }
    function a() { console.log(4) }
    //预解析过程中,当变量和函数同名时:只留下函数的值,不管谁前谁后,所以函数优先级更高;
    //正常解析
    console.log(a) //function a() { console.log(4) }
    a = 1;
    console.log(a) //1
    a = 3;
    console.log(a) //3
    console.log(a) //3
}

四、作用域

定义:它是指对某一变量和方法具有访问权限的代码空间, 在JS中, 作用域是在函数中维护的。表示变量 或函数起作用的区域,指代了它们在什么样的上下文中执行,亦即上下文执行环境。

ES5的作用域只有两种:全局作用域和局部作用域

全局变量和局部变量同名的坑

  • 在全局变量和局部变量不同名时,其作用域是整个程序。
  • 在全局变量和局部变量同名时,全局变量的作用域不包含同名局部变量的作用域。
window.onload = function () {
  //作用域:表示变量或函数起作用的区域
  //全局作用域
  var a = 1;
  function fn(){
    console.log(a)
  };

  //局部作用域
  function fn(){
    var a = 1;
    console.log(a)
  };

  //扩展
  var a = 1; //全局作用域
  function f1(){
   // a = 10;  //修改全局作用域的值
   var a = 10;  //局部作用域
  };
  f1();
  console.log(a); //1

  //{}内定义的变量就是局部变量吗?
  var a = 1;
  if(true){
    var a = 10;
  };
  console.log(a);//10


 var a = 1;
 a = 10;
 a = 30;
 var a = 40;
 console.log(a);//40

 var a = 10;
 var a;
 console.log(a); //10

//  全局变量和局部变量同名的坑
//  (1)在全局变量和局部变量不同名时,其作用域是整个程序。
//  (2)在全局变量和局部变量同名时,全局变量的作用域不包含同名局部变量的作用域。

var c = 10;
function f2(){
    var c;
    console.log(c)  //undefined
};
f2();

//扩展题:
var b = 10;
function f3(){
    console.log(b); //undefined
    var b = 100; 
};
f3();//

//解析过程
//预解析
var b;
function f3(){};

//逐行解析
b = 10;  //全局变量b不会作用于f3函数的
f3();
//函数执行,内部预解析
var b;  //在全局变量和局部变量同名时,全局变量的作用域不包含同名局部变量的作用域。
//逐行解析
console.log(b);
b = 100;


//作用域链
//当执行函数时,先从函数内部寻找局部变量,没有找到,会向创建函数的作用域寻找,依次向上
var a = 1;
function f1(){
    var a = 10;
    function f2(){
        var a = 20;
        console.log(a)  //f2=>f1=>window
    };
    function f3(){
        console.log(a)  //f3=>f1=>window
    };
    f2();
    f3();
}
f1();
console.log(a); //window


//扩展题
var a = 1;
function f1(){
    var b = 2;
    function f2(){
        var c = b;
        b = a;
        a = c;
        console.log(a,b,c)
    };
    f2();
};
f1();

//高频面试题
var a = 1;
function fn(){
    console.log(a); //全局变量的值
    a = 2; //修改全局变量
};
fn();
console.log(a);


//第二题
var a = 10;
function fn(){
    console.log(a);
    a = 100;
    console.log(a);
    var a;
    console.log(a);
};
fn();
console.log(a);
//undefined 100 100 10
//解析过程
//预解析
var a;
function fn(){}

//逐行解析
a = 10;
fn();
//函数解析
//预解析
var a;
//逐行解析
//全局变量和局部变量同名的坑
console.log(a); 
a = 100;
console.log(a);  //局部变量的值
console.log(a);  //局部变量的值

console.log(a); //全局变量的值


//第三题
var a = 10;
function f1(){
    var b = 2*a;
    var a = 20;
    var c = a+1;
    console.log(b);
    console.log(c);
};
f1();
}

五、this指针

this:它代表函数运行时,自动生成的一个内部对象,只能在函数内部使 用,随着函数使用场合的不同,this的值会发生变化,指向是不确定的,也就是说是可以动态改变的; 但是有一个总的原则,那就是this指的是,调用函数的那个对象。

window.onload = function () {
    //this
    //this是一个指针型变量,它动态指向当前函数的运行环境,随着函数使用场合不同,会发生变化

    //JS函数类型的多样化
    //1)普通函数
    // function fn(){
    //     console.log(this)  //window
    // };
    // fn();
    //严格模式 'use strict'
    // function fn(){
    //     'use strict'
    //     console.log(this)  //undefined
    // };
    // fn();

    //2)内置函数 setTimeout
    // function fn(){
    //     console.log(this)   //window
    // };
    // //延迟执行
    // setTimeout(fn,2000);

    //3)回调函数 :一个函数作为参数传递给另一个函数
    // function fn(v){
    //     console.log(v)
    // };

    // function fn2(callback,value){
    //     callback(value)
    // }
    // fn2(function(v){console.log(this)},'hello');  //window

    //4)函数在数组中
    // function fn(){
    //     console.log(this)
    // };
    // var arr = [fn,2,3,{id:10}];
    // arr[0](); //this指向arr  有调用者
    // var f = arr[0];
    // f(); //this指向window  没有调用者


    //5)函数在对象object中
    function fn(){
        console.log(this)
    };
    var o2 = {
        id:1,
        init:fn
    };
    o2.init(); //this指向o2对象   有调用者


    //this指向规则:
    //判断有没有调用者:1)没有调用者,this指向window   2)有调用者,指向调用者

    //6)构造函数   生成对象的模板
    var arr = new Array();
    var obj = new Object();

    function Person(name,age){
        this.name = name;
        this.age = age;
        this.action = function(){
            console.log(this.age)
        }
    };
    var Jack = new Person('Jack',20); //实例化
    Jack.action()

    //创建新的实例,通过new操作符,经历以下步骤:
    //1、创建新的对象
    //2、将构造函数的作用域赋给新对象 (this指向新对象)
    //3、执行构造函数中的代码
    //4、返回新对象


    //函数的作用域可以修改或指定吗?
    //call()  apply()  bind() //都是JS用于改变函数内部this的值
    var id = 1;
    function fn(){
        console.log(this.id)
    };
    var item = {
        id:888
    };
    var person = {
        id:999,
        name:'jack'
    };
    var obj = {
        id:100,
        action:fn
    };
    //this指向为item
    fn.apply(item); //fn函数在item对象中执行
    fn.call(item);  //888

    //this指向person
    fn.apply(person);
    fn.call(person);

    obj.action();  //this.id = obj.action()
    //改变obj.action的this指向
    obj.action.call(person);

    //apply和call的区别:
    //区别在于传递参数的方式
    var a = 'global';
    function fn(title,value){
        return `${this.a} ${title} ${value}`  // return this.a + ''+title+''value;
    };
    fn('global',100);

    var obj  = {
        a :555,
    };

    var obj2  = {
        a :666,
        action:fn
    };
    fn.call(obj,'call',111); //'555 call 111'
    fn.apply(obj,['apply',222]); //'555 apply 222'
    //obj2.action this指向为window
    obj2.action.call(window,'obj2',333)//'global obj2 333'

    //bind 用于创建一个新的函数,该函数的上下文被绑定到指定的对象,但并不会立即执行。返回新的函数
    var id = 1;
    function fn(){
        console.log(this.id)
    };
    var item = {
        id:888
    };
    var item2 = {
        id:999
    }
    fn.bind(item)();  //相当于item.fn()
    var f = fn.bind(item);  //创建一个新的函数
    //f();  函数执行
    f.call(item2); //改变新函数的this指向  通过apply call不能生效 结果 888

    //扩展
    var n = 100;
    var obj = {
        n:1,
        render:function(){
            this.n+=1;
            console.log(this.n)
        }
    };
    //setInterval(obj.render, 2000); //101 102 103 ...
    // 2 3 4 5 6
    //setInterval(obj.render.bind(obj), 2000);
}

六、事件

事件冒泡和捕获

当事件发生后,这个事件就要开始传播(从里到外或者从外向里)。

为什么要传播呢?因为事件源本身(可能)并没有处理事件的能力,即处理事件的函数(方法)并未绑 定在该事件源上。

例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须 从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数 的名字,就是让这个函数去处理该按钮的click事件)

document.getElementById("d1").onclick = function(e){ 
    console.log("d1");
}

事件委托:就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素

window.onload = function () {
    //事件冒泡和事件捕获 && 事件委托
    //事件冒泡:从里到外——button => div  => body  => document
    //事件捕获:从外向里——document => body => div => button
    //添加div1 div2 div3触发事件
    function f(name) {
        return document.querySelector(name)
    };
    // f('.div1').onclick = function(){
    //     console.log('div1')
    // };
    // f('.div2').onclick = function(){
    //     console.log('div2')
    // };
    // f('.div3').onclick = function(e){ //e 代表event对象   事件对象
    //     //阻止事件冒泡
    //     e.stopPropagation();
    //     console.log('div3');
    // };

    //event事件对象
    //event.target 是获取触发事件对象的目标  (触发事件的元素)
    //event.currentTarget 绑定事件的元素
    f('.div1').onclick = function (e) {
        //this.style.color = 'red';  //this绑定事件,在div1上添加style="color: red;"
        //e.target.style.color = 'red'; //event.target 触发事件的元素,触发div3 在div3上添加style="color: red;"
        e.currentTarget.style.color = 'red'; //event.currentTarget 绑定事件的元素,触发div3 在div1上添加style="color: red;"
    };
    //绑定事件 div1
    //触发事件 div3


    //事件委托
    //1、傻傻地一个个去领取
    // var list = document.getElementById("list");
    // var li = list.getElementsByTagName("li");
    // for (var i = 0; i < li.length; i++) {
    //     li[i].onclick = function () {
    //         this.style.color = 'red';
    //     }
    // }

    //2、委托 将事件绑定到父节点,点击子节点通过事件冒泡到父并处理
    var list = document.getElementById("list"); //委托 UL
    list.onclick = function (e) {  //绑定UL
        if (e.target.nodeName === 'LI') {  //节点名字
            e.target.style.color = 'red'; //event.target 触发事件的元素
        }
    }
}

七、原型

定义:用原型实例指向创建对象的类,使用于创建新的对象的类的共享原型的属性与方法。原型是一个对象,其它对象可以通过它实现属性继承

JS对象分两种:普通对象object和函数对象function

  • prototype是函数才有的属性
  • __proto__是每个对象都有的属性
window.onload = function () {
    //原型&原型链   原型是一个对象,其它对象可以通过它实现属性继承
    //为什么要有原型对象 
    //需求创建对象
    //初始化想法,一个一个对象创建,缺点:不灵活,对象之前没有关联
    var o1 = {
        name:'o1',
        value:1
    };
    var o2 = {
        name:'o2',
        value:2
    };
    var o3 = {
        name:'o3',
        value:3
    };

    //扩展:工厂模式  大批量创建对象
    function fn(name,value){
        return {
            name,
            value
        }
    };
    var o4 = fn('o4',4);
    var o5 = fn('05',5);
    //特点:每个对象实例都是独立的,没有共享的原型对象

    //扩展:构造函数  就是一个普通函数,但是内部使用this变量,使用new运算符来生成实例对象
    //生成对象的模板
    function Fn2(name,value){
        this.name = name;
        this.value = value;
    }
    var o6 = new Fn2('o6',6);
    var o7 = new Fn2('07',7);
    //优点:继承——将方法或属性定义在构造函数的原型对象上,实现多个对象之前共享方法和属性

    //总结:通过在构造函数的原型对象上添加属性和方法,实现共享
    function Fn2(name,value){
        this.name = name;
        this.value = value;
    };
    Fn2.prototype.msg = 'hello';  //在原型对象上添加属性msg
    Fn2.prototype.greet = function(){   //在原型对象上添加方法
        console.log(`${this.msg} , my name is ${this.name}`)
    }
    var o8 = new Fn2('o8',8); //o8对象是被Fn2创建
    var o9 = new Fn2('o9',9);
    o8.greet()

    //msg属性和greet方法被定义在Fn2构造函数的原型对象上
    
    //msg属性和greet方法为什么不定义在构造函数上,为什么要写在原型对象上?
    // function Fn2(name,value){
    //     this.name = name;
    //     this.value = value;
    //     this.msg = 'hello';
    //     this.greet = function(){
    //         console.log(`${this.msg} , my name is ${this.name}`)
    //     }
    // };
    // var o8 = new Fn2('o8',8); //o8对象是被Fn2创建
    // var o9 = new Fn2('o9',9);
    // o8.greet();

    //谁才拥用原型对象?
    //对象分二种:
    function f(){}
    var o = {};
    typeof f;

    // 1、每一个函数对象都有一个prototype属性,但是普通对象是没有的;
    // prototype下面又有个constructor,指向这个函数。
    // 2、每个对象都有一个名为__proto__的内部属性,指向它所对应的构造函数的原型对象,原型链基于
    // __proto__;
    function Person(){};//定义函数对象
    Person.prototype.name='Jack';
    Person.prototype.age='18'; //原型对象,可以被所有的实例对象共享
    var p1 = new Person();
    var p2 = new Person();
    p1.name;
    p2.age;


    //扩展
    function Cat(name,color){
        this.name = name;
        this.color = color;
    };
    Cat.prototype.type='animal';
    Cat.prototype.eat = function(){
        console.log('吃老鼠')
    };
    var Jack = new Cat('Jack','white'); //Jack = {name:'Jack',color:'white'};
    var Tom = new Cat('Tom','black');
    Jack.constructor  //Jack是被谁构造出来
    Jack.value = 'jack';  ////Jack = {name:'Jack',color:'white',value:'jack'};
    Jack.type = 'type';  //Jack对象添加type属性  {name:'Jack',color:'white',value:'jack',type:'type'};
    Tom.type  //'animal'

    
    Jack.__proto__ //{type: 'animal', eat: ƒ, constructor: ƒ}
    //获取原型对象属性
    Jack.__proto__.type //'animal'
    //修改原型对象的值,共享的属性修改了
    Jack.__proto__.type = '888';
    //另一个实例对象受到影响
    Tom.type //'888'


    //另一种理解
    // function Cat(name,color){
    //     this.name = name;
    //     this.color = color;
    // };
    // Cat.prototype = {
    //     constructor:Cat,
    //     type:'animal',
    //     eat:function(){
    //         console.log('吃老鼠')
    //     }
    // };


    //原型链  找妈妈的过程
    function Animal(){  //构造函数 Animal
        this.type ='Animal';
        this.hobby = ['吃','睡'];
    }
    Animal.prototype.xyz = 'xyz';
    function Cat(name,color){ //构造函数 Cat 
        this.name = name;
        this.color = color;
    };
    Cat.prototype = new Animal();  //
    var Jack = new Cat('Jack','white'); //Jack = {name:'Jack',color:'white'};
    var Tom = new Cat('Tom','black');
    //Jack.type
    //Tom.xyz
    Jack.__proto__ //指向它所对应的构造函数的原型对象
    //修改animal中hobby
    Jack.__proto__.hobby[1] = '玩';
    //修改xyz的值为999
    Jack.__proto__.__proto__.xyz = 999;


    //扩展
    function F1(){
        this.title1 = 'f1'
    };
    function F2(){
        this.title2 = 'f2'
    };
    function F3(){
        this.title3 = 'f3'
    };
    F2.prototype = new F1();
    F3.prototype = new F2();
    var f = new F3();
    f.title1
    f.title2 
    f.title3

    //常见面试题
    function Animal(){  //构造函数 Animal
        this.type ='Animal';
    }
    Animal.prototype.xyz = 'xyz';
    function Cat(name,color){ //构造函数 Cat 
        this.name = name;
        this.color = color;
    };
    Cat.prototype = Animal.prototype;  
    var Jack = new Cat('Jack','white'); 
    var Tom = new Cat('Tom','black');
    console.log(Tom.type)  //undefined
}

八、继承

继承的几种常见方式

  • 原型继承
  • 构造函数的继承
  • 组合继承
window.onload = function () {
    //继承
    //第一种:原型继承
    //对象与对象产生关联
    function Animal(){ //
        this.type = 'animal';
        this.eating = function(food){
            console.log(food)
        }
    };
    //定义猫对象
    function Cat(name,color){
        this.name = name;
        this.color = color;
    };
    Animal.prototype.global='global';
    Cat.prototype = new Animal();  //原型继承
    //实例化
    var c1 = new Cat('Lily','white'); //c1 = {name:'Lily',color:'white'}
    var c2 = new Cat('Niki','black');

    //定义狗对象
    function Dog(age){
        this.age = age;
    };
    Dog.prototype = new Animal();  //原型继承
    var d1 = new Dog(6);
   // d1.eating('000');

    c1.__proto__.type = 'c1'; //修改原型对象的值
    c1.__proto__.__proto__.global='c1'; //修改原型对象父父的值
    // console.log(c2.__proto__.__proto__); 
    // console.log(d1.__proto__); 
    // console.log(d1.__proto__.__proto__); 
    //原型继承,无限到根对象,缺点:不能修改原型对象,会影响所有的实例

    //构造函数继承  call()  apply()
    function Animal(){ //
        this.type = 'animal';
        this.eating = function(food){
            console.log(food)
        }
    };
    //定义猫对象
    function Cat(name,color){
        Animal.apply(this);  //Animal对象在Cat对象中执行  将Animal对象绑定在子对象上
        this.name = name;
        this.color = color;
    };

    var c1 = new Cat('Lily','white'); //c1 = {name:'Lily',color:'white'}
    var c2 = new Cat('Niki','black');
    c1.__proto__.type = 'c1';
    c2.type = 'c2';
    console.log(Cat.prototype);
    console.log(c1.type);  
    console.log(c2.type);  

    //组合继承
    function Animal(){ //
        this.type = 'animal';
    };
    //定义猫对象
    function Cat(name,color){
        Animal.call(this);  //Animal对象在Cat对象中执行  将Animal对象绑定在子对象上
        this.name = name;
        this.color = color;
    };
    Cat.prototype = new Animal();
    var c1 = new Cat('Lily','white'); //c1 = {name:'Lily',color:'white',type:'animal"}
    var c2 = new Cat('Niki','black'); //c2= {name:'Niki',color:'black',type:'animal"}
    c1.type='c1'; //c1对象的属性  //c1 = {name:'Lily',color:'white',type:'c1"}
    c2.__proto__.type = 'c2';
    console.log(c1.type); // c1
    console.log(c2.type); ////c2= {name:'Niki',color:'black',type:'animal"}
    console.log(c1.__proto__.type);  //c2
}

九、递归

递归,就是在运行的过程中调用自己。

window.onload = function () {
    //递归是什么
    // 程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一
    // 个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。

    // 构成递归需具备的条件:
    // 1. 子问题须与原始问题为同样的事,且更为简单;
    // 2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。

    //累加  1-5
    //循环
    function sum(n){
        var count = 0;
        for(var i=0;i<=n;i++){
            count += i;
        };
        return count;
    };
    sum(5)

    //递归
    function sum2(n){
        if(n>=1){
            return n+ sum2(n-1)
        }
        return 0;
    };
    sum2(5)
    //简化
    // function sum2(n){
    //     return n>=1?n+ sum2(n-1):0;
    // };
    // sum2(5)

    //递归,有递和归,有去(递去)和有回(归来)
    //执行过程
    //1、传入5----if成立----return 5 + sum2(5-1)【自调】,此时代码等待
    //1、传入4----if成立----return 4 + sum2(4-1)【自调】,此时代码等待
    //1、传入3----if成立----return 3 + sum2(3-1)【自调】,此时代码等待
    //1、传入2----if成立----return 2 + sum2(2-1)【自调】,此时代码等待
    //1、传入1----if成立----return 1 + sum2(1-1)【自调】,此时代码等待
    //1、传入0----if不成立----return 0,条件结束,开始回溯

    //递归要满足4个条件:
    // 1、函数必须有参数
    // 2、函数的参数必须改变
    // 3、函数必须调用自身
    // 4、函数有结束条件

    function sum2(n){  //1、函数必须有参数
        if(n>=1){  //4、函数有结束条件
            return n+ sum2(n-1)  //2、函数的参数必须改变 3、函数必须调用自身
        }
        return 0; //结束返回值
    };
    sum2(5)
}

十、闭包

要理解闭包,首先要理解javascript的全局变量和局部变量。

javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

定义:闭包就是能够读取其他函数内部变量的函数。

由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在 一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁

window.onload = function () {
    //要理解闭包,首先要理解javascript的全局变量和局部变量。
    // javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部
    // 的局部变量。
    //全局变量——存在window对象下
    var n = 100;
    function fn() {
        n++;  //修改全局变量
        console.log(n)
    };
    fn(); // 101
    fn(); // 102
    fn(); // 103

    //局部变量
    function fn2() {
        var count = 100;
        count++;
        console.log(count)
    };
    fn2();  //101
    fn2();  //101
    fn2(); //101
    fn2(); //101

    //变量特色:全局变量维持变量的状态,局部变量不会造成污染。

    //有没有这样一种机制,既可以长久的保存局部变量又不会造成全局污染
    //闭包就是能够读取其他函数内部变量的函数。
    //闭包可以简单理解成“定义在一个函数内部的函数“。
    //闭包是将函数内部和函数外部连接起来的桥梁
    function f1() {
        var a = 10;  //局部变量
        function f2() {
            a++;
            console.log(a)
        };
        f2();
    };
    f1(); //11
    f1(); //11

    //单纯只是为了外部访问局部变量
    function f3() {
        var a = 10;
        return a;
    };
    f3();

    //闭包的写法
    function f1() {
        var a = 10;  //局部变量
        function f2() { //函数内部定义f2函数
            a++;  //局部变量修改
            console.log(a);
        };
        return f2;  //返回f2
    };
    //f1(); //调用时,返回f2 
    //f2() { //函数内部定义f2函数
    //     a++;  //局部变量修改
    //     console.log(a);
    // }
    var f = f1();  //将f1()函数执行的结果赋给变量f,相当于f2赋 给了f变量
    f(); //相当于f2()
    f(); //12
    f(); //13

    //f1()执行结果就是闭包
    //闭包形成条件:返回一个函数,并且这个函数对局部变量存在引用,这就形成闭包的包含关系,可以维持变量的状态


    //简化的写法
    function f1() {
        var a = 10;  //局部变量
        return function() { //函数内部定义f2函数
            a++;  //局部变量修改
            console.log(a);
        };
    };
    var x = f1();  
    var y = f1();
    x(); //11
    x(); //12
    x(); //13
    y(); //11  一个闭包内对变量的修改,不会影响到另外一个闭包中的变量
    y(); //12
    x(); //14
    y(); //13

    // //vue2
    // new Vue({
    //     el:'#my',
    //     data:{
    //         name:'jack',
    //         id:1,
    //         age:30
    //     },
    //     methods:{
    //         action:function(){

    //         }
    //     },
    //     mounted:function(){

    //     }
    // })

    // //vue组件中  每一组件中都有data
    // {
    //     data(){
    //         return {
    //             name:1
    //         }
    //     }
    // }

    //平时有用过闭包
    function Fn(){
        var name = 'jack';
        return {
            getName:function(){
                return name;
            },
            setName:function(newName){
                name = newName;
            }
        }
    };
    var obj = Fn();
    //obj.getName();
    obj.setName('xyz');
    obj.getName();
}

十一、客户端存储

  • sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面 重新加载
  • localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在
  • Cookie 是一些数据, 存储于你电脑上的文本文件中,用于存储 web 页面的用户信息

localStorage、sessionStorage常用方法和属性 用法上基本一致

# 1、存储  
window.localStorage.setItem(key,value)  
localStorage.key= value; 

# 2、获取  
window.localStorage.getItem(key) 
localStorage.key 

# 3、删除某个数据  
window.localStorage.removeItem(key) 

# 4、删除全部数据  
window.localStorage.clear() 

# 5、长度属性  window.localStorage.length; 

Cookie 数据是以键值对的形式存在的,每个键值对都有过期时间。如果不设置时间,浏览器关闭, cookie就会消失,当然用户也可以手动清除cookie

Cookie每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

Cookie内存大小受限,一般每个域名下是4K左右,每个域名大概能存储50个键值对

# 1、存储  
document.cookie = "name=xiaosuo" 

# 2、获取  document.cookie