JavaScript
文章目录
- JavaScript
- 1. JavaScript 数据类型
- 2. Undefined出现场景
- 3. NUll类型
- 4. undefined与null的相同点和不同点
- 5. Boolean类型转换
- 6. Number类型
- 7. isNaN与Number.isNaN
- 8. String 类型
- 9. 字符串处理
- 10. 比较运算符
- 11. typeof运算符
- 12. 判断对象是否为空对象
- 13. 判断是否为空数组
- 14. 流程控制中switch的比较
- 15. 引用数据类型
- 16. new 关键字的原理
- 17. 判断对象的属性自己身上没有而原型身上有
- 18. Object.create的源码
- 19. 判断对象的属性是否可枚举
- 20 newObject的注意点
- 21. 模拟new操作符
- 21. 获取引用类型的原型名称
- 22 使用reduce实现统计数组中每个元素的出现次数
- 23. es6的扩展运算符实现最大值最小值
- 24. 手写find函数
- 25 手写filter函数
- 26. 手写some函数
- 27. every函数
- 28. 书写map函数
- 28. 手写reduce函数
- 29. 函数定义的三种方式
- 30. arguments对象
- 31. 构造函数与普通函数的区别
- 32. 作用域和作用域链
- 33 变量被赋值全局变量,啥也不干报错
- 34. 函数提升
- 35. 闭包
- 36.this指向
- 37 apply的使用场景
- 38 call的使用场景
- 39. 手写Call函数
- 40 手写apply
- 41. 手写bind
- 对象
- DOM
- AJAX
- ES6
1. JavaScript 数据类型
- 普通数据类型:Undefined,Boolean,Number,NUll,String,Symbol(es6新增)
- 复杂数据类型:Function,Array,Object,Date(),Map,Set
2. Undefined出现场景
- 变量只声明,不初始化
- 获取一个对象不存在的属性
- 接收一个没有返回值的函数结果
- 函数的实参不传递或者实参个数小于形参
3. NUll类型
- 定义:一个空指针对象
- 出现场景
- 变量初始化,赋值为null
- 获取dom元素,获取不到为null
- 正则表达式匹配不到,返回为null
4. undefined与null的相同点和不同点
相同点
- 字面值都是只有一个,undefined–>undefined,null–>object, typeof 检测
- 转换为Boolean类型都是false----> null==undefined
- 不能转换成对象—>抛出typeError异常
不同点
-
null是一个关键字,而undefined是一个全局变量—>挂载在window对象或global对象上
-
数值转换 null+1=1,undefined+1=NaN
5. Boolean类型转换
数值型
- NaN,0—>false,其他true
String
- ‘’–>false,其他true
Function
- true
Object
- null—>false,其他true
6. Number类型
- 抛异常情况
var num=0xah h超过了16进制标识
类型转换(Number()函数)
- undefined—>NaN
- null—>0
- true—>1
- false—>0
- ‘’—>0
- ’ '—>0
- ‘12’—>12
- ‘012’—>12
- ‘0012’—>12
- ‘00.12’—>0.12
- ‘0x10’—>16
- ‘123abc’—>NaN
- object—>调用对象的valueof方法—>上面不行调用toString方法
parseInt函数
-
语法规则:parseInt(字符串,进制)
-
作用:解析一个字符串返回数字
-
如果传入不是字符串,则隐式转换成字符串再进行解析
-
‘abc’—>NaN
-
‘fg123’,16—>15(前缀匹配,f满足16进制声明,后面g第一个不匹配,直接舍弃)
-
12—>12
-
‘12*2’—>12
-
12*2—>24
-
‘12.97’—>12
-
与map连用的注意点
let arr=['1','2','3','4']
let result=arr.map(parseInt)
// 等价于
let result=arr.map((item,index)=>{
parseInt(item,index) // 这里第二个参数出现问题
'1',0--->1 原样返回
'2',1---->NaN 不符合规范
'3',2---->NaN
'4',3---->NaN
})
parseFloat
-
字符串解析成浮点数
-
参数只有一个,无进制概念
-
前置匹配
parseFloat(' 2.3')---->2.3
'12.23.33'----->12.23
-----
无进制概念
'fg123'---->NaN
7. isNaN与Number.isNaN
-
NaN和谁都不相等
-
isNaN:判断一个值是否能被转换成数字类型(es5)
- 其中存在隐式转换的过程
- ‘abc’–>NaN true
- ‘undefined’—>NaN true
- NaN—>Nan true
- {}---->NaN true
-
Number.isNaN:直接判断值是否为NaN(es6添加)
- 只有NaN才会返回true,解除了二义性
8. String 类型
创建字符串的方式
- 字面量创建:let str=‘123’
- String() :let str=String(‘123’)
- new String():let str=new String(‘123’)
区别
- 字面量创建和String()创建的是基本字符串,比较时只比较值
- new String()创建的返回的一个对象实例,比较时比较的是内存地址
String实例对象特性
- 原型链上由许多方法
- 基本字符串也可以调用(为什么)
- 调用是隐式转换(自动装配成String包装类型)
9. 字符串处理
- 字符串转数组—> arr.split(“”)
- 数组翻转:arr.reserve()
- 字符串下标字符:str.chatAt(i)
- 字符串下标ascall:str.charAtCode(i)
- 下标的值,str.indexOf(‘char’)
- 倒着来下标的值,str.lastIndexOf(‘char’)
10. 比较运算符
三等于运算符
- 先比较值的类型是否相同
- 再比较值是否相同
// Number存在类型转换
1===Number(1) true
// new出来的是基本数据类型的包装类型
1===new Number(1) false
//
'hello'===String('hello') // 都是基本类型true
双等于运算符
- 比较更加复杂
- 先比较值的类型是否相同
- 不相同则进行隐式类型转换,转换为相同的类型,再进行值的比较
隐式转换规则
- 字符串和数值—>字符串转换为数值
- 一方为boolean类型,boolean类型转换为数值
11. typeof运算符
-
type of class :注意是function
-
typeof 6/2 :NaN
-
typeof (6/2):number
12. 判断对象是否为空对象
-
JSON.stringify(obj)===‘’
-
hasOwnProperty
let obj={}
function isEmpty(obj){
for(key of obj){
if(obj.hasOwnProperty(key)){
return false
}
}
return true
}
13. 判断是否为空数组
arr instanceof Array && arr.length<=0
14. 流程控制中switch的比较
- 为===比较
15. 引用数据类型
- 堆内存,栈内存存放地址
- 通过new关键字创建
16. new 关键字的原理
function Person(username,sex){
this.username=username
this.sex=sex
// 这里默认return this
}
// 等价于
// new 关键字做的事情
var person={}
person.__proto__=Person.prototype
Person.call(person)
17. 判断对象的属性自己身上没有而原型身上有
function propertyInPrototype(obj,pro){
return !obj.hasOwnProperty(pro)&& pro in obj
}
18. Object.create的源码
- 用于寄生继承(创建一个空对象,对象的原型指向要继承的原型)
Object.create=function(proto,protoProperty){
// 构造函数
function F(){
}
F.prototype=proto
// 第二个参数一个对象
if(protoProperty){
Object.defineProperties(F, protoProperty)
}
return new F()
}
19. 判断对象的属性是否可枚举
console.log(stu.propertyIsEnumerable("sayHello")); // false :sayHello属于原型上的函数
//将userName属性设置为不可枚举
Object.defineProperty(stu, "userName", {
enumerable: false,
});
20 newObject的注意点
const a={age:16}
const b=new Object(a)
b===a // true
21. 模拟new操作符
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.sayHi=function(){
console.log('666')
}
function New(){
var obj={}
Person.call(obj,arguments)
obj.__proto__=Person.prototype
return obj
}
New("name",'18').sayHi()
21. 获取引用类型的原型名称
instanceof
var arr=[]
arr instanceof Array
判断它最近一层的构造函数
var arr=[]
arr.__proto__.constructor === Array
使用Object的toString方法
var arr=[]
Object.prototype.toString.call(arr)
兼容性解决isArray方法
if(!Array.isArray){
Array.isArray=function(args){
return Object.prototype.toString().call(args)==='[object Array]'
}
}
22 使用reduce实现统计数组中每个元素的出现次数
function getCount(arr){
return arr.reduce((pre,next)=>{
pre[next]? pre[next]++:(pre[next]=1)
return pre
},{})
}
23. es6的扩展运算符实现最大值最小值
var arr=[1,3,6,9,20]
Math.min(...arr) // 获取最小值
Math.max(...arr) // 获取最小值
24. 手写find函数
Array.prototype.myFind=function(callback){
// 判断callback是否为函数
if(typeof callback !=='function'){
throw new Error('参数不是函数')
}
// this为方法调用者,也就是数组本身
let length=this.length
for(let i=0;i<length;i++){
let res=callback.call(this,this[i],i)
if(res){
// 找到了
return this[i]
}
}
// 找不到
return undefined
}
25 手写filter函数
Array.prototype.myFilter=function(callback){
// 判断callback是否为函数
if(typeof callback !=='function'){
throw new Error('参数不是函数')
}
let length=this.length
// 返回值为一个新的数组
let arr=[]
for(let i=0;i<length;i++){
let res=callback.call(this,this[i],i)
if(res){
// 满足条件--->加入数组
arr.push(this[i])
}
}
return arr
}
26. 手写some函数
Array.prototype.myFilter=function(callback){
// 判断callback是否为函数
if(typeof callback !=='function'){
throw new Error('参数不是函数')
}
let length=this.length
// 返回值为一个boolean值
let flag=false
for(let i=0;i<length;i++){
let res=callback.call(this,this[i],i)
if(res){
// 满足条件--->加入数组
flag=true
break;
}
}
return flag
}
27. every函数
- 和some基本一致
28. 书写map函数
Array.prototype.myFilter=function(callback){
// 判断callback是否为函数
if(typeof callback !=='function'){
throw new Error('参数不是函数')
}
let length=this.length
// 返回值为一个新的数组
let newArr=[]
for(let i=0;i<length;i++){
// 注意这里为修改后的对象
let res=callback.call(this,this[i],i)
// 我们需要进行深拷贝解除引用的关系
// 我们对象才进行深拷贝
if(typeof res==='object'){
let newItem=JSON.parse(JSON.stringify(res))
}
newArr.push(newItem)
}
return newArr
}
28. 手写reduce函数
Array.prototype.myFilter=function(callback,initValue){
// 判断callback是否为函数
if(typeof callback !=='function'){
throw new Error('参数不是函数')
}
let length=this.length
// 判断数组长度
if(length<=0){
// 空数组
if(initValue){
return initValue
}else{
return undefined
}
}
let i=0
// 判断是否存在初始值
if(!initValue){
initValue=this[i]
i++
}
for(i;i<length;i++){
// 注意这里为新的initValue
initValue=callback.call(this,initValue,this[i],i)
}
return initValue
}
29. 函数定义的三种方式
- 函数声明
- 函数表达式
- 构造函数
// 构造函数的形式,最后一个参数为函数体
var fn=new Function('num1','num2','return num1+num2')
构造函数创建函数的特点
- 效率较低
- 作用域为顶级作用域
var a=12
function fn(){
var a=11
return new Function('return a')
}
fn()() // 结果为12 为顶级作用域的a
30. arguments对象
-
argument只存在于函数作用域中
-
结构是伪数组,可通过下标访问
-
内部添加argument通过下标添加,不会发生变化
应用
- 可以判断参数个数
- 可以实现方法重载,参数可以不同,但都可以通过arguments对象获取
31. 构造函数与普通函数的区别
- 构造函数一般首字母大写
- 构造函数需要配合new使用
- 构造函数内部可以使用this指向挂载
- 构造函数的执行过程(this执向,默认返回this)
32. 作用域和作用域链
- 作用域:一个变量定义的调用的范围
- 作用域链,查找变量时先查找当前作用域,找不到就往外层作用域进行查找,查找的过程就形成了作用域链
33 变量被赋值全局变量,啥也不干报错
// 报错
(function(){
console.log(str)
str='hello world'
})()
// hello world
(function(){
str='hello world'
console.log(str)
})()
34. 函数提升
- 特殊例题
var a=true
foo()
function foo(){
if(a){
var a=20
}
console.log(a)
}
// 结果为undefined
// 分析程序等价于
function foo(){
var a;
if(a){
a=20
}
console.log(a)
}
function foo(){
var a=1
function b(){
a=10
return
function a(){}
}
b()
console.log(a)
}
foo()
// 1
// 代码等价于
function foo(){
var a;
function b(){
function a(){}
a=10
return
}
a=1
b()
console.log(a) // a先找自己作用域a,b里面的a是全局作用域的
}
35. 闭包
- 可以读取外部变量的函数
出现原因
- 内部函数维持了对外部变量的引用
应用场景
-
变量私有:只能是内部方法(维持对外部作用域变量的引用)进行调用修改其值
-
解决延时器的问题,var循环通过立即执行函数生成闭包,维持对外部作用域的变量(形参)的引用
特殊题,注意this指向和闭包
var userName = "zhangsan";
var person = {
userName: "lisi",
method: function () {
return function () {
return this.userName;
};
},
};
console.log(person.method()()); //zhangsan
- 闭包+作用域(从函数定义区域向外找闭包)
function create() {
var a = 100;
return function () {
console.log(a);
};
}
var fn = create();
var a = 200;
fn(); // 100
function print(fn) {
var a = 200;
fn();
}
var a = 100;
function fn() {
console.log(a); // 100
}
print(fn);
复杂的一道题
var num = 10; // window.num=10
var obj = { num: 20 }; // obj.num=20
obj.fn = (function (num) {
this.num = num * 3; // 立即执行 window.num=60
num++; // 立即执行 obj.num=21
return function (n) {
this.num += n; // fn(5) wind.num=65 // fn(10) obj.num=30
num++; // fn(5) obj.num=22 // fn(10) 23
console.log(num); // fn(5) 22 //fn(10)23
};
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
36.this指向
- 闭包+this指向
<button id="btn">获取用户信息</button>
<script>
var userInfo = {
data: [
{ userName: "zhangsan", age: 20 },
{ userName: "lisi", age: 21 },
],
getUserInfo: function () {
this.data.forEach(function (p) {
// 这里的this,会进行作用域的查找,一直往外找,直到找到this(window)
// forEach的源码,是不是对传入的参数,没有进行this指向的绑定,否则this将会是this.data
console.log(this);
});
},
};
var btn = document.getElementById("btn");
// btn.onclick = userInfo.getUserInfo;
btn.onclick = userInfo.getUserInfo.bind(userInfo);
</script>
- 处理方法,使用that保存this
- 使用箭头函数
37 apply的使用场景
- apply的传递参数为一个数组,可以改变原函数的传参方式
// 如Math.max方法
// 原本的调用方法
Math.max(1,2,3,4,5,6)
// 修改为数组传参的方式
Math.max.apply(null,[1,2,3,4,5,6])
38 call的使用场景
- 伪数组转数组
function fn(){
let arr=Array.prototype.slice.call(arguments)
}
-
组合继承时改变this指向
-
立即执行函数时的this执行
var person = [
{ id: 1, userName: "zhangsan" },
{ id: 2, userName: "lisi" },
];
for (var i = 0; i < person.length; i++) {
(function (i) {
// 这里的this指向window
this.print = function () {
console.log(this.id);
};
this.print();
})(i);
}
// 要想实现打印person中对象的值,需要修改立即执行函数中的this
(function(i){
}).call(person[i],i)
39. 手写Call函数
// 原理,在传递进来的this指向上(挂载一个方法)(这个方法为call函数调用者,即代替调用,实现this指向修改)
Add.call(sub,1,2,3)
Function.prototype.myCall=function(context){
// 获取除this外的所有参数
let newArguments=Array.prototype.slice.call(arguments,1)
// call函数的调用者(是一个函数)
let startPerson=this
// 新的this指向
let newThis=context||window // 没传,默认window
// 我们用新的this指向 调用函数(startPerson)
newThis.fn=startPerson
return newThis.fn(...newArguments)
}
40 手写apply
// 原理,在传递进来的this指向上(挂载一个方法)(这个方法为call函数调用者,即代替调用,实现this指向修改)
Add.call(sub,1,2,3)
Function.prototype.myCall=function(context){
// call函数的调用者(是一个函数)
let startPerson=this
// 新的this指向
let newThis=context||window // 没传,默认window
// 我们用新的this指向 调用函数(startPerson)
newThis.fn=startPerson
// 和call不同的地方,apply只有两个参数
if(arguments[1]){
// 第二个参数
return newThis.fn(...arguments[1])
}else{
return newThis.fn()
}
}
41. 手写bind
// 只返回一个函数,不调用
Function.prototype.myBind=function(context){
// 参数传递与call相同
// 获取除this外的所有参数
let newArguments=Array.prototype.slice.call(arguments,1)
// call函数的调用者(是一个函数)
let startPerson=this
// 新的this指向
let newThis=context||window
// 返回值为一个函数
return function(){
// 这里还有一个问题,返回的函数调用时,如果传递参数
// 是不是要加进去
let bindArguments=Array.prototype.slice.call(arguments)
// 这个函数一执行,就调用apply函数
return startPerson.apply(newThis,newArguments.concat(bindArguments))
}
}
对象
42. 对象属性定义
- Object.defineProperty
定义某个属性的特性
// 使属性的值变得不可修改
let person={}
Object.defineProperty(person,"age",{
writable:false
})
定义对象的get和set方法
// 定义私有变量,不想让外界直接修改,提供get和set方法进行访问
let person={
_age:18
}
Object.defineProperty(person,"age",{
get:function(){
return this._age
},
set:function(newValue){
this._age=newValue
}
})
43. 对象的创建
- 字面量
- 工厂函数
- 问题:没有原型,大家都是Object
- 希望的结果,Person数据类型,Teacher数据类型
function factory(){
let o= new Object()
return 0
}
- 构造函数
- 原型上挂载一类对象的公共属性和方法(原型函数)
- 类
44. 深浅拷贝
浅拷贝:拷贝前后值类型互不影响,引用类型会受到影响
function shallowCopy(src){
let dst={}
// 这里会遍历到继承的属性
for(let val in src){
if(src.hasOwnProperty(val)){
dst[val]=src[val]
}
}
return dst
}
- 使用Object.assign实现对象的浅拷贝
Object.assign(dst,src) // 注意这里拷贝的是对象的可枚举的类型变量
深拷贝:在浅拷贝的基础上,对引用数据类型的拷贝,拷贝前后互不影响
- 使用JSON.stringify 和 JSON.parse
// 这种方法存在的问题
// 1. 拷贝时:属性是函数是,新对象没有改函数属性
// 2. 新的对象的原型变成了Object
// 3. 对象的属性存在循环引用时,会抛异常
- 自己手写一个解决上述问题
let map=new WeakMap()
function deepCopy(src){
// 基本思路,如果属性是引用类型,继续递归调用改函数
// 如果是基本数据类型,返回原来的值
// 其实可以继续完善,如Array单独处理
// obj类型我们就通过__proto__.constructor.name获取原型类
// 对创建的{} 我们让其__proto__ 为 类的prototype
// 循环引用的问题,存储一个键值对,键位prop属性名,值为属性值
if(typeof src==='object'){
let newObj=Array.isArray(src)? []:{}
// 遍历对象的所有属性,递归调用deepCopy
// 判断是否循环引用
if(map.get(src)){
return src
}
// 存储引用
// 其实就是看这个引用是否地址是否已经被创建
// 如果已经创建,则让拷贝的新对象(某个属性)直接指向即可
// 没有创建,也就是新对象某个属性的值(需要重新创建引用)
map.set(src,newObj)
for(prop of src){
//
newObj[prop]=deepCopy(src[prop])
}
return newObj
}else{
return src
}
}
45. 原型对象的重写
- 如果我们想在一个原型上同时添加属性和方法,又不想写多行代码,我们直接赋值一个对象
- 问题就是原型关系被破坏
- 加上constructor属性解决
function Person() {}
Person.prototype = {
constructor: Person, //添加constructor
userName: "zhangsan",
age: 20,
sayHi: function () {
console.log(this.userName);
},
};
var person = new Person();
person.sayHi();
console.log(Person.prototype.constructor);// Person
46. 继承
- 原型链继承
//1. 优点:实现简单,子类的实例可以访问原型链上的所有属性和方法
// 2. 缺点
// 所有实例将共享父类的的属性和方法
// 如果父类的构造函数有参数,参数无法传递 new Student('123')
- 构造函数继承
// Person.call(this)
//1. 优点 解决子类向父类传递参数
// 2. 子类不能访问父类的原型方法(原型链没有改变)
- 拷贝继承
// 把父类的属性和方法都赋值给子类
// 1. 首先判断属性和方法在原型上还是在实例上
// 2. 根据位置的不同,赋值给子类的地方也不同
function Person(age){
this.age=age
this.sayHi=function(){
...
}
}
Person.prototype.run=function(){}
// 子类拷贝属性和方法
// 构造方法参数按需传入
function Student(id,age){
let person=new Person()
for(let key of person){
if(person.hasOwnProperty(key)){
this[key]=person[key]
}else{
// 原型上
Student.propertype[key]=person[key]
}
}
this.id=id
}
// 优点,可以继承属性和方法,且互不干扰
// 可以向父类传递参数
// 本质是没有修改原型链,而是手写一个相同的类除了名字不同,
// 本来是重写一遍,现在用代码实现了循环写
- 组合继承
- 构造函数继承+原型链继承
问题是子类的实例的__proto__为父类的一个实例,要父类的方法,这个父类的实例只是一个跳板,并无实际作用
// 也就是,我们一个创建一个空对象,让该对象的__proto__指向父类即可
// 也就是寄生组合继承
- 寄生组合继承
// 定义Super构造函数
function Super() {}
//Super.prototype原型对象指向了Person.prototype
Super.prototype = Person.prototype;
//Student.prototype原型对象指向了Super的实例,这样就去掉了Person父类的实例属性。
Studnet.prototype = new Super();
Studnet.prototype.constructor = Studnet;
var student = new Studnet(1001, 21);
- 或者使用Object.create(Person) 创建一个对象,该对象的原型为Person
- 即
obj.__proto__=Person.prototype
- 即
47. jQuery模拟实现
var $=(jQuery=function(){
return jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(){
return this // 这个this即为jQuery.prototype
},
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
$().version
- 作用域污染问题
- this.length=0 会使原型上的length=0
var $=(jQuery=function(){
return jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(){
this.length=0
return this // 这个this即为jQuery.prototype
},
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
$().version
- 解决,将init方法返回一个实例(实例的
__proto__
为原型),而不是原型
var $=(jQuery=function(){
return new jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(){
this.length=0
this._size=function(){
return this.length
}
},
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
// 原型链修改
jQuery.fn.init.prototype=JQuery.fn
$().version
48. jQuery实现选择器
// selector 选择器
// context 上下文
var $=(jQuery=function(selector,context){
return new jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(selector,context){
// 设置默认值
selector=selector||document
context=context||document
if(selector.nodeType){
// 是DOM元素
this[0]=selector
this.length=1
this.context=selector
return this
}
if(typeof selector==='string'){
// 字符串
// 这里应该使用正则表达式判断各种选择器
//如果选择器是一个字符串
var e = context.getElementsByTagName(selector); // 获取指定名称的元素
//通过for循环将所有元素存储到当前的实例中
for (var i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length; //存储元素的个数
this.context = context; //保存上下文对象
return this; //返回当前的实例
}else{
// 空
this.length = 0;
this.context = context;
return this;
}
},
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
// 原型链修改
jQuery.fn.init.prototype=JQuery.fn
$().version
49. jQuery实现html
// selector 选择器
// context 上下文
var $=(jQuery=function(selector,context){
return new jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(selector,context){
// 设置默认值
selector=selector||document
context=context||document
if(selector.nodeType){
// 是DOM元素
this[0]=selector
this.length=1
this.context=selector
return this
}
if(typeof selector==='string'){
// 字符串
// 这里应该使用正则表达式判断各种选择器
//如果选择器是一个字符串
var e = context.getElementsByTagName(selector); // 获取指定名称的元素
//通过for循环将所有元素存储到当前的实例中
for (var i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length; //存储元素的个数
this.context = context; //保存上下文对象
return this; //返回当前的实例
}else{
// 空
this.length = 0;
this.context = context;
return this;
}
},
// 原型上挂载方法
html:function(val){
// 是不是要把选择器选中的所有元素都添加同样的内容
jQuery.each(this,function(val){
this.innerHtml=val
},val)
}
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
// 实现隐式迭代html的方法
jQuery.each=function(object,callback,agrs){
for(let i=0;i<object.length;i++){
callback.call(object[i],args)
}
return object
}
// 原型链修改
jQuery.fn.init.prototype=JQuery.fn
$().version
$().html("<h1>666</h1>")
50 jQuery实现extend
// selector 选择器
// context 上下文
var $=(jQuery=function(selector,context){
return new jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(selector,context){
// 设置默认值
selector=selector||document
context=context||document
if(selector.nodeType){
// 是DOM元素
this[0]=selector
this.length=1
this.context=selector
return this
}
if(typeof selector==='string'){
// 字符串
// 这里应该使用正则表达式判断各种选择器
//如果选择器是一个字符串
var e = context.getElementsByTagName(selector); // 获取指定名称的元素
//通过for循环将所有元素存储到当前的实例中
for (var i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length; //存储元素的个数
this.context = context; //保存上下文对象
return this; //返回当前的实例
}else{
// 空
this.length = 0;
this.context = context;
return this;
}
},
// 原型上挂载方法
html:function(val){
// 是不是要把选择器选中的所有元素都添加同样的内容
jQuery.each(this,function(val){
this.innerHtml=val
},val)
}
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
// 实现隐式迭代html的方法
jQuery.each=function(object,callback,agrs){
for(let i=0;i<object.length;i++){
callback.call(object[i],args)
}
return object
}
// 原型链修改
jQuery.fn.init.prototype=JQuery.fn
// 在原型上添加扩展的方法
jQuery.extend=jQuery.fn.extend=function(obj){
for(let prop in obj){
this[prop]=obj[prop]
}
return this
}
// 调用
jQuery.fn.extend({
text:function(val){
jQuery.each(this,function(val){
this.innerText=val
},val)
}
})
$().version
$().html("<h1>666</h1>")
51. extend 实现多个参数传递
// selector 选择器
// context 上下文
var $=(jQuery=function(selector,context){
return new jQuery.fn.init() // 返回jQuery原型
})
jQuery.fn=jQuery.prototype={
init:function(selector,context){
// 设置默认值
selector=selector||document
context=context||document
if(selector.nodeType){
// 是DOM元素
this[0]=selector
this.length=1
this.context=selector
return this
}
if(typeof selector==='string'){
// 字符串
// 这里应该使用正则表达式判断各种选择器
//如果选择器是一个字符串
var e = context.getElementsByTagName(selector); // 获取指定名称的元素
//通过for循环将所有元素存储到当前的实例中
for (var i = 0; i < e.length; i++) {
this[i] = e[i];
}
this.length = e.length; //存储元素的个数
this.context = context; //保存上下文对象
return this; //返回当前的实例
}else{
// 空
this.length = 0;
this.context = context;
return this;
}
},
// 原型上挂载方法
html:function(val){
// 是不是要把选择器选中的所有元素都添加同样的内容
jQuery.each(this,function(val){
this.innerHtml=val
},val)
}
verson:"7.1",
length:1,
size:function(){
return this.length
}
}
// 实现隐式迭代html的方法
jQuery.each=function(object,callback,agrs){
for(let i=0;i<object.length;i++){
callback.call(object[i],args)
}
return object
}
// 原型链修改
jQuery.fn.init.prototype=JQuery.fn
// 在原型上添加扩展的方法
jQuery.extend=jQuery.fn.extend=function(obj){
// 多个参数的处理
if(typeof arguments[0]==='string'&&typeof arguments[1]==='string'){
var destination = arguments[0],
source = arguments[1];
//把第二个对象合并到第一个参数对象中,并返回合并后的对象
for (var property in source) {
destination[property] = source[property];
}
return destination
}else{
for(let prop in obj){
this[prop]=obj[prop]
}
return this
}
}
// 调用
jQuery.fn.extend({
text:function(val){
jQuery.each(this,function(val){
this.innerText=val
},val)
}
})
// 实现样式的修改
jQuery.fn.extend({
fontStyle:function(obj){
var defaults = {
color: "#ccc",
size: "16px",
};
//如果有参数,会覆盖掉默认的参数
defaults = jQuery.extend(defaults, obj || {});
//为每个DOM元素执设置样式.
jQuery.each(this, function () {
this.style.color = defaults.color;
this.style.fontSize = defaults.size;
});
}
})
$().version
$().html("<h1>666</h1>")
DOM
52. 选择器
<body>
<div>
<h4>标题内容</h4>
<span>span标签内容</span>
<p>
段落内容
<span>段落中的第一个span标签</span><br />
<span>段落中的第二个span标签</span>
</p>
</div>
</body>
<script>
console.log(document.querySelector("p span").innerHTML);// 获取p标签中第一个span标签中的内容,所以输出结果为:段落中的第一个span标签
console.log(document.querySelector("h4,span").innerHTML);//获取第一个h4或者是span元素的内容:所以输出结果为:标题内容
var ele = document.querySelector("p");
console.log(ele.querySelector("div span").innerHTML);//段落中的第一个span标签。
// 首先先找到`p`元素,然后看一下p元素下面有没有div,我们发现没有,但是依然能够匹配到span元素。
//原因是:在匹配的过程中会优先找出最外层div元素下的span元素的集合,然后在判断span元素是否属于p元素的子元素,最后返回
//第一个匹配到的span元素的值。
</script>
注意点
- CSS选择器是先从目标元素开始查询,然后再筛选出满足条件的元素,所有有些结果让我们难以理解
var ele = document.querySelector("p");
console.log(ele.querySelector("div span").innerHTML);//段落中的第一个span标签。
// 首先先找到`p`元素,然后看一下p元素下面有没有div,我们发现没有,但是依然能够匹配到span元素。
//原因是:在匹配的过程中会优先找出最外层div元素下的span元素的集合,然后在判断span元素是否属于p元素的子元素,最后返回
//第一个匹配到的span元素的值。
53. HTMLCollection对象与NodeList对象
HTMLCollection
- 具有length对象
- 可以通过item()和namedItem()函数访问特定元素
<div id="container">
<div class="bar"></div>
<div class="foo">
<div class="inner"></div>
</div>
</div>
<script>
var main = document.getElementById("container").children;
console.log(main); //HTMLCollection
console.log(main.item(0)); //输出:<div class="bar"></div>
console.log(main.item(1)); // 输出:foo元素
</script>
- namedItem根据元素上的name值获取
<form id="form1">
<input type="text" id="userName" />
<input type="password" id="password" name="userPwd" />
</form>
<script>
var form1 = document.getElementById("form1").children;
console.log(form1.namedItem("userPwd"));// <input type="password" id="password" name="userPwd" />
</script>
NodeList
- 具有length属性
- item方法
相同点
- 结构都是伪数组,转换为真正的数组
Array.prototype.slice.call(htmlCollection||NodeList)
不同点
- NodeList存储多种节点,文本,注释,元素节点
- 而HTMLCollection只有元素节点
- children获取HTMLCollection
- childNodes获取NodeList
54. DOM操作
- 一些不常见的操作
- 创建属性:
createAttribute("type")
- 设置属性节点:
newInput.setAttributeNode(newAttr);
- 设置文本节点:
var newTextNode = document.createTextNode("用户密码"); form1.appendChild(newTextNode); //添加文本节点
- 创建属性:
//创建一个input元素
var newInput = document.createElement("input");
//创建属性
var newAttr = document.createAttribute("type");
newAttr.value = "password";
//将属性绑定到元素上
newInput.setAttributeNode(newAttr);
//创建一个文本节点
var newTextNode = document.createTextNode("用户密码");
form1.appendChild(newTextNode); //添加文本节点
form1.appendChild(newInput);
- 删除属性操作:
removeAttribute
<form id="form1">
用户名<input type="text" id="userName" /> <br />
用户密码<input type="password" id="password" name="userPwd" />
</form>
<script>
var input = document.querySelector("#userName");
input.removeAttribute("id");
</script>
修改元素节点
- 元素替换
var father=document.querySelector("#container")
// 将father中的div标签替换为p标签
var div=father.querySelector("div")
var newP=document.createElement("p")
// 元素节点替换
father.replaceChild(newP,div)
54 优化dom操作
- 使用文档碎片
let ul=document.querySelector("ul")
let frag=document.createDocumentFragment()
for(let i=0;i<100;i++){
document.createElement("li")
li.innerHtml=`item{i}`
// 插入到文档碎片
frag.appendChild(li)
}
ul.appendChild(frag)
55. 事件捕获阶段,目标阶段,冒泡阶段
事件捕获
- 从根节点开始,事件向内传播,直到目标节点
事件冒泡
- 从目标节点开始,事件向外传播,直到根节点
设置事件的传播顺序
-
xxx.addEventListen("click",function(){},true)
:事件以捕获方式传播 -
第三个参数设置为flase,以冒泡方式传播,默认为false
阻止事件的冒泡
- event.stopPropagation():只阻止事件冒泡
- event.stopImmediatePropagation():不仅阻止事件冒泡,还阻止绑定在当前元素上的其他事件的执行
56 事件对象
- 形参 :event
- window.event
// 兼容性处理
return event||window.event
57 触发事件的目标对象
// IE event.srcElement
// 其他event.taget 或者 同时都可以
return event.target||event.srcElement
58 事件模型
DOM0
- 一个函数赋值一个事件处理函数
<button onclick="fn()">点击</button>
// 或者
let btn=document.querySelector("button")
btn.onclick=function(){
}
- 缺点,一个事件(如click触发时),只有一个事件处理函数有效,后绑定生效
DOM2
- 不同浏览器厂商制定的不同的事件绑定方式
// IE 10以下
// 绑定
element.attachEvent('on'+eventName,handler)
// 取消绑定
element.detachEvent('on'+eventName,handler)
// 其他浏览器或者IE10+
addEventListener(eventName,handler,useCapture) //添加事件处理程序
removeEventListener(eventName,handler,useCapture) // 删除事件处理程序
- 特性:一个事件可以绑定多个事件处理函数
- 移除绑定时不能使用匿名函数,需要绑定和移除时传入相同的函数
- addEventListen
- 多个事件处理函数顺序执行
- this指向绑定的元素
- attachEventListen
- 多个事件处理函数倒序指向
- this指向window
浏览器兼容性处理
let eventHandle={
addEventListener:function(ele,type,handler){
if(ele.addEventListener){
ele.addEventListener(type,handler)
}else if(ele.attachEvent){
ele.attachEvent("on"+type,handler)
}else{
ele["on"+type]=handler
}
}
}
DOM3
- 允许自定义事件
var customeEvent
//立即执行函数,隔离作用域
(function(){
// 判断是否支持dom3事件模型
if(document.implemention.hasFeature("CustomEvents","3.0")){
let user={userName:"zhangsan"}
// 创建一个事件
customeEvent=document.createEvent("CustomEvent")
// 初始化一个事件,事件名,是否冒泡,是否可以被取消,e.detail绑定值
customeEvent.initCustomEvent("myEvent",true,false,user)
}
})()
// div盒子监听自定义事件
div.addEventListener("myEvent",function(e){
// 初始化时user对象的属性被写入e.detail中
console.log(e.detail.userName)
})
// 一个按钮实现自定义事件的触发
btn.addEventListener("click",function(){
div.dispatch(customeEvent)
})
59 事件委托
- 基于事件冒泡的机制,将本应注册到子元素的事件处理函数注册到父元素上
- 父元素对子元素的触发进行统一处理
60 浏览器的重绘和重排
浏览器渲染HTML的过程
- HTML代码被解析成DOM树,CSS代码被解析成样式规则集
- DOM树和样式规则集合并形成渲染树
- 根据渲染树进行节点的属性计算,如位置,大小,颜色
- 进行节点的渲染
重排
- 更改页面布局的一种操作
重排的发生场景
- 第一次渲染
- 浏览器窗口大小变化
- 元素的大小改变,影响周围
- 元素的增删
重排的发生机制
-
浏览器维护一个重排队列,里面存放一定量引起重排操作的样式改变,到达一定程度后统一进行一次重排
-
强制重排的情况
- 获取元素的样式
clicent系列 offset系列 scroll系列 width,height
重绘
- 改变元素在页面中的显示样式
重绘与冲重排的关系
- 重排一定引发重绘,而重绘不一定引发重排
减少重绘和重排的操作
- 统一添加样式,添加类
- 少使用table布局,多次引发重排
- 对一个元素需要进行复杂的样式添加时,先隐藏改元素,添加号后在显示
- 文档碎片
- 事件委派
AJAX
61 AJAX的基本使用
// 兼容性处理
let xhr=null
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest()
}else{
xhr=new ActiveXObject("Microsoft.XMLHTTP")
}
// 开始发起请求
// 第三个参数为是否为异步
xhr.open("post","xxx.url",true)
// post请求需要设置请求头
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")
xhr.open("name=zs&age=18")
// 监听结果
xhr.onreadystatechange=function(){
if(xhr.readyState===4&&xhr.status===200){
console.log(xhr.responseText)
}
}
// readyState
// 0 初始化,未发送
// 1 open 未send
// 2 send 未响应
// 3.接收,响应部分
// 4.完成
62. AJAX的优缺点
优点
- 异步,无刷新请求
- 前后端分离
缺点
- 不利于SEO,页面内容JavaScript动态生成
- 破环了统一资源定位符的效果
- 相同的url地址,不同的人看到的效果不同
63. get和post请求
get
- 请求参数在地址栏
- 参数的大小有限制
post
- 传输大数据
- 参数在请求体,不在地址栏
64. 浏览器的同源策略
-
禁止不同页面的dom操作
- 防止iframe跨域,恶意网站嵌套正规网站,恶意网站操作正规网站的dom,获取用户信息
-
禁止XHR实现不同源的页面请求
- CSRF攻击:跨站请求伪造
- 恶意网站发起请求时,获取浏览器上存储的cookie,用这个cookie来去请求正规网站,正规网站返回信息被恶意利用
- CSRF攻击:跨站请求伪造
65 跨域
- cors跨域
- jsonp跨域
- 只支持get请求
- 在url的参数中添加回调函数,返回的结果为回调函数的执行
// 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
window.onload = function () {
var btn = document.getElementById("btnLogin");
btn.addEventListener("click", function () {
sendRequest();
});
};
function sendRequest() {
var userName = document.getElementById("userName").value;
//请求参数,其中包含回调函数
var param = "name=" + userName + "&callback=successFn";
//请求的url
var url = "http://localhost:3000/getUserNameInfo?" + param;
var script = document.createElement("script");
script.src = url;
document.body.appendChild(script);
}
function successFn(result) {
console.log("result=", result);
}
</script>
</head>
<body>
用户名:<input type="text" id="userName" /> <br />
<button id="btnLogin">登录</button>
</body>
</html>
- 后端
var express = require('express')
var app = express();
// app.all('*', function (req, res) {
// //设置可以接收请求的域名
// res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
// res.header('Access-Control-Allow-Methods', 'GET, POST,PUT');
// res.header('Access-Control-Allow-Headers', 'Content-Type');
// res.header('Content-Type', 'application/json;charset=utf-8');
// req.next();
// })
app.get('/getUserNameInfo', function (req, res) {
var userName = req.query.name;
//获取请求的回调函数
var callbackFn = req.query.callback
console.log('callbackFn==',callbackFn)
console.log('userName=',userName)
var result = {
id: 10001,
userName: userName,
userAge:21
};
var data = JSON.stringify(result);
res.writeHead(200, { 'Content-type': 'application/json' })
//返回值是对对回调函数的调用
res.write(callbackFn+'('+data+')')
// res.write(data);
res.end()
})
app.listen(3000, function () {
console.log('服务端启动....')
})
ES6
66 . let 与 var
let
- 不存在变量提升
- 块级作用域(大括号包裹的代码块)
var
- 存在变量提升
- 只有全局作用域和函数作用域
var引发的问题
var temp = new Date();
function show() {
console.log("temp=", temp)
if (false) {
var temp = "hello world";
}
}
show();
- 输出undefined,temp变量提升导致
- if(false) 里面是块级作用域,而var不认识这个
67. 块级作用域
- 直接使用块级作用域
{
let temp='xxx'
console.log(temp)
}
- 之前使用立即执行函数创建独立(私有空间)
(function(){
var temp='xxx'
console.log(temp)
})()
- 循环打印
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log('i=', i);
}, 1000)
}
- 立即执行函数
for (var i = 0; i < 3; i++) {
(function(i){
setTimeout(function() {
console.log('i=', i);
}, 1000)
})(i)
}
68. let暂时性死区
- 在区域内存在let声明,通过let声明的变量一开始就形成了一个封闭的作用域,在声明之前访问变量都是不被允许的
产生原因
- let不存在变量提升
68. let 不允许重复声明
- 错误示例
function test() {
let num = 12;
let num = 20;
console.log(num)
}
test()
function test() {
var num = 12;
let num = 20;
console.log(num)
}
test()
69. const
-
声明常量
-
不存在变量提升
-
块级作用域有效
-
暂时性死区
-
不允许重复声明
70. 解构赋值
let arr = [{
userName: 'zs',
age: 18
},
[1, 3], 6
];
let [{
userName,
age
},
[num1, num2], num3
] = arr;
console.log(userName, age, num1, num2, num3);
解构不成功,返回undefined
let [num1, num2] = [6]
console.log(num1, num2); // num2=undefined
不完全解构
// 如果只取第一个值呢?
let [num1] = [1, 2, 3];
console.log(num1); //1
//只取第二个值呢?
let [, num, ] = [1, 2, 3];
console.log(num); //2
// 只取第三个值呢?
let [, , num] = [1, 2, 3];
console.log(num); //3
71. 对象解构赋值
let {
userName: name,
userAge: age
} = {
userName: 'ls',
userAge: 20
}
console.log(name, age);
// 将userName和userAge解构出来,并重命名为name和age
默认解构
let obj={
username:'zhangsan'
}
let {name,age=20}=obj
console.log(name,age)
// age本来应该是undefined,但是赋值了默认值,所有age=20
嵌套结构对象的解构
let obj={
arr:["hello",{
msg:'world'
}]
}
// arr并不是一个变量,只是一个标志
let {arr:[str,{
msg
}]}=obj
console.log(str,msg)
// 再看一个案例
let obj = {
local: {
start: {
x: 20,
y: 30
}
}
};
let {
local: {
start: {
x,
y
}
}
} = obj;
console.log(x, y);
字符串的解构赋值
let [a,b,c,d,e,f]='itcast'
console.log(a,b,c,d,e,f)
// 解构length属性
let {
length:len
}='itcast'
console.log('len',len)
函数参数的解构
function test([x,y]){
return x+y
}
test([3,6])
72. 解构赋值的好处
交换变量的值
let num1=3
let num2=4
[num1,num2]=[num2,num1]
函数可以返回多个值
function test(){
return [1,2,3]
}
const [a,b,c]=test()
console.log(a,b,c)
函数返回对象
function test(){
return {
num1:3,
num2:4
}
}
let {num1:res1,num2:res2}=test()
73. 扩展运算符(将数组分离成一个个参数)
- …arr
数组合并
let arr1=[1,2,3]
let arr2=[4,5,6]
let newArr=[].concat(arr1,arr2)
// 使用扩展运算符
let newArr2=[...arr1,...arr2]
代替apply方法
// 调用Math.max方法是,需要传递的参数为一个列表
// 通过apply使传递的参数变成一个数组
let arr=[1,2,87,7]
Math.max.apply(null,arr)
// 可以直接使用展开运算符
Math.max(...arr)
73- rest运算符(将剩余参数组合成数组)
function add(...values) {
console.log(values); // 5
}
add(2, 3);
切割变量
let arr=[1,2,3,4,5,6]
const [num1,...arr1]=arr
// num1 1
// arr1 [2,3,4,5,6]
代替arguments
function test(...values){
// 直接调用数组的方法
values.sort()
}
// 之前的arguments参数
function test(){
Array.prototype.slice.call(arguments).sort()
}
74. 扩展运算符和剩余运算符的区分
扩展运算符
- 将数组分离成一个个变量
- 出现在实参和赋值操作的右边
剩余运算符
- 将一个个变量组合成一个数组
- 出现在形参和赋值操作的左边
75. 箭头函数
直接返回对象
let f=()=>({
a:1,
b:2
})
this指向问题
// 箭头函数没有自己的this指向
// 那其中的this指向哪里呢?
// 1. 找到箭头函数定义的位置,当前作用域
// 2. 定义位置的上一层作用域的上下文(this) 即为this指向
- 一个案例
let person = {
userName: 'wangwu',
getUserName() {
setTimeout(() => {
console.log(this.userName);
},1000)
}
}
person.getUserName();
-
那么在我们这个案例中,
setTimeout
函数中使用了箭头函数,箭头函数中用了this,
而这时this
指的是外层代码块也就是person
,所以箭头函数中使用的this指的就是person
(包含箭头函数最近的函数是setTimeout
,那么包含setTimeout
这个函数的最近的函数或者是对象是谁呢?对了,是getUserName
这个函数,而getUserName
这个函数是属于哪个对象呢?是person
,所以this
为person
) -
let person = { userName: 'zhangsan', getUserName() { return () => { console.log(this.userName); } } } person.getUserName()(); // zhangsan
箭头函数的特性
-
不能使用call,apply,bind改变this指向
- 为什么:其实就是把这个箭头函数赋值给新的this指向,this中就有一个对象使这个箭头函数,而箭头函数中查找this又去当前对象的父亲哪里找,就没有达到修改this的目的
-
不能使用new操作符
- 还是this的问题
-
没有自己的
prototype属性
-
原型上的函数(prototype.methodxxx)不要写成箭头函数
- 还是this问题
76. ES6中属性和方法的简写
let userName = 'zhangsan';
let userAge = 18;
let person = {
// userName:userName
// userAge:userAge
userName,
userAge,
// 原本
// sayHello:function(){}
sayHello() {
console.log('Hello');
}
}
person.sayHello();
77 浅拷贝
- Object.assign(target,src1,src2…)
属性同名后面覆盖前面
不可枚举的属性无法拷贝
let obj1={}
let obj2={a:1,b:2}
Object.defineProperty(obj2,"c",{
enumerable:false
})
78 Symbol
- 为了防止命名冲突的问题
let s=Symbol()
let s2=Symbol()
console.log(s) // Symbol()
console.log(s2) // Symbol()
- 传入字符串来表示区别不同的Symbol
let s=Symbol('s')
let s2=Symbol('s2')
console.log(s) // Symbol('s')
console.log(s2) // Symbol('s2')
// 唯一标识
console.log(Symbol('s')===Symbol('s')) // false
- symbol作为属性名
let s=Symbol("s")
let obj={
// 2
[s]:"hello"
}
// 1
obj[s]="hello"
//3
Object.defineProperty(obj,s,{
// 属性
...
})
- 防止属性名覆盖
let obj = {
name: 'zs',
age: 18
}
let mySymbol = Symbol('lib1');
function test1(obj) {
obj[mySymbol] = 42;
}
let mySymbol2 = Symbol('lib2');
function test2(obj) {
obj[mySymbol2] = 369;
}
test1(obj);
test2(obj);
console.log(obj);
79. Proxy
- 对象前面的拦截层(代理),在对莫格对象进行访问或者处理时,先经过这个拦截层,代理这次操作
Proxy语法
let proxy=new Proxy(target,handler)
// target 要进行拦截的对象
// handler 拦截的处理对象
get拦截(拦截对get)
let student={
userName:'张三'
}
let proxy=new Proxy(student,{
// target,目标对象
// property 对象的属性
get:function(target,property){
// 对属性进行判断,看是否存在
if(property in target){
return target[property]
}else{
throw new Error("属性不存在")
}
}
})
// 访问属性(注意是proxy,而不是student)
console.log(proxy.userName)
console.log(proxy.userAge)
set拦截
let student={
name:'zs',
age:18
}
let proxy=new Proxy(student,{
// target 目标对象
// prop:属性名
// value:属性值
set:function(target,prop,value){
// 对要设置的值进行合法性的校验
if(prop==='age'){
if(!Number.isInteger(value)){
throw new TypeError('年龄不是整数')
}
if(value>60){
throw new RangeError('年龄太大了')
}
}
}
})
// 访问属性(注意是proxy,而不是student)
proxy.age='80'
console.log(proxy.age)
应用场景1—值的校验
class Person{
constructor(){
this.name=''
this.age=19
// 返回的是一个代理对象
return validator(this,personValidators)
}
}
// 校验规则器
const personValidators={
name(val){
return typeof val==='string'
},
age(val){
return typeof val==='number'&&val>18
}
}
// 实现代理的处理函数handler
function validator(target,validator){
// target 为实例对象
// validator 校验器
return new Proxy(target,{
_validator:validator,
// 拦截赋值操作
// target 实例对象
// key 属性
// value 值
set(target,key,value){
// 判断修改的值是否在对象中
if(target.hasOwnProperty(key)){
let vFn=this._validator[key]
// 进行校验
if(vFn(value)){
// 通过
return Reflect.set(target,key,value)
}else{
}
}
}
})
}
应用场景2-vue3响应式原理
<script>
// 获取输入框和p标签dom
let input=document.querySelector("#input")
let p=document.querySelector("#p")
// 定义一个响应式的对象,即proxy对象
let obj={
text:''
}
let proxyObj=new Proxy(obj,{
set(target,key,value){
// 判断属性是否存储
if(target.hasOwnProperty(key)){
// 存在,数据驱动视图
input.value=value
p.innerHtml=value
}else{
throw new Error("属性不存在")
}
}
})
// input的键盘事件也触发数据驱动视图
input.addEventListener("keyup",function(e){
newObj.text=e.target.value
})
</script>
应用场景-实现私有属性
const obj={
_id:555,
// 提供对外的get和set方法
getUserId(){
return this._id
},
setUserId(val){
this._id=val
}
}
// 使用proxy代理实现变量私有,不允许直接修改和获取
let proxyObj=new Proxy(obj,{
get(target,key){
// 判断读取的属性是否在target,并且不是下划线开头
if(target.hasOwnProperty(key)&&key[0]!=='_'){
return target[key]
}else{
return undefined
}
},
set(target,key,value){
// 赋值时保证属性存在,且不是下划线开头
if(target.hasOwnProperty(key)&&key[0]!=='_'){
target[key]=value
}
}
})
80 set
-
长度:
new Set().size
-
转换为数组
Array.from(new Set())
-
数组到set
new Set(arr)