前端提升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);
}
三、预解析
定义:可以理解为把变量或函数预先解析到它们被使用的环境中
解析过程
会预先解析关键字var、function等
提前赋值
- var a = 10 提前解析 var a;(此时a的值为undefined
- 函数,在正式运行代码前,赋值为整个函数块
console.log(fn) //function变量提升 function fn(){console.log("123")} function fn(){ console.log("123") }
预解析结束后,浏览器再逐行解读代码
//看看下面的代码输出结果 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