实例对象可以通过__proto__.constructor
指向构造函数,实际上是先借助__proto__
指向原型对象prototype
,然后再通过原型对象的.constructor
指向构造函数。
// 1、只要是原型对象就有__proto__原型,指向原型对象。
// 2、Star原型对象里面__proto__原型指向的是Object.prototype
// 3、Object原型对象里面__proto__原型指向的是null ,即已经到了最顶层
1、当访问一个对象的属性或者方法时,首先会查找这个对象自身有没有该属性
2、如果没有就去该对象的原型对象中查找(也就是`__proto__`指向的`prototype`原型对象)
3、如果还没有就查找原型对象的原型(即`Object`的原型对象)
4、以此类推一直找到`Object`为止(null)
1、在构造函数中,this指向实例对象
2、原型对象函数里面的this,指向的是实例对象
// 扩展求和方法
Array.prototype.sum = function () {
let sum = 0;
for (let i = 0; i < this.length; i++){
sum += this[i];
}
return sum
};
错误示范
// 会报错,会把原先的方法覆盖掉
Array.prototype= {
sum: function () {
let sum = 0;
for (let i = 0; i < this.length; i++){
sum += this[i];
}
return sum
}
}
调用这个函数,并且修改函数运行时的this指向
fun.call(thisArg, arg1, arg2, ...)
thisArg:当前调用函数this的指向对象(当前调用的函数this指向谁)
arg1、arg2:传递的其他参数
ES6之前并没有提供extends继承,我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承
核心原理
通过call()把父类的this指向子类的this,这样就可以实现子类继承父类的属性
// 借用父构造函数继承属性
// 1、父构造函数
function Father(uname, age) {
// this指向父构造函数的实例对象
this.name = uname;
this.age = age
}
Father.prototype.money = function (){
console.log('父亲要挣钱')
};
// Son.prototype = Father.prototype; 直接赋值会有问题,如果
// 子原型对象发生变化,那么父原型对象也会变化
// 2、子构造函数
function Son(uname, age, score) {
// this指向子构造函数的实例对象
Father.call(this, uname, age,);
this.score = score
}
// 这个是子构造函数专门的方法
// 将Father实例对象赋值给Son原型对象,此时,Father实例对象和Father原型对象
处于不同的存储地址中,所以不会造成数据混乱
// 又因为Father实例对象可以通过__proto__访问Father原型对象,所以Son原型对象
可以使用Father原型对象中的money方法
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数
Son.prototype.constructor = Son;
Son.prototype.exam = function (){
console.log('孩子要考试');
}
let son = new Son('ckw',18, 100);
// console.log(son);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);
类的本质还是一个函数,我们也可以简单的认为 类 就是构造函数的另外一种写法
// ES6之前通过 构造函数+原型对象 实现面向对象变成
// 1、构造函数有原型对象prototype
// 2、构造函数原型对象prototype里面有constructor指向构造函数本身
// 3、构造函数可以通过原型对象添加方法
// 4、构造函数创建的实例对象里面有__proto__原型指向构造函数的原型对象
class Star{
}
console.log(typeof Star);
// ES6之后通过 类 实现面向对象编程
// 1、类也有原型对象prototype
console.log(Star.prototype);
// 2、类的原型对象prototype里面也有constructor指向构造函数本身
console.log(Star.prototype.constructor);
// 3、类也可以通过原型对象添加方法
Star.prototype.sing = function (){
console.log('我会唱歌')
}
let ldh = new Star();
console.dir(ldh);
// 5、类创建的实例对象也有__proto__原型指向类的原型对象
console.dir(ldh.__proto__ === Star.prototype);
// 所以ES6的类就是语法糖
迭代(遍历)方法:forEach()、map()、filet()、some()、every()
1、array.forEach(function(currentValue, index, arr))
2、 array.filter(function(currentValue, index, arr))
filter()方法会创建一个新的数组,新数组的元素是经过筛选以后的数组
// filter筛选方法
let arr = [11,22,33,44,20];
let newArr = arr.filter(function (value, index){
return value >= 20;
})
console.log(newArr);
3、array.some(function(currentValue, index, arr){})
let arr = [11,22,33,4];
let flag = arr.some(function (value, index){
return value < 3;
})
console.log(flag);
let arr1 = ['red', 'pink', 'yellow']
let flag1 = arr1.some(function (value){
return value === 'red';
})
console.log(flag1);
// filter也是查找满足条件的元素,但返回值是一个数组,而且会返回所有符合条件的元素
// some的返回值是true/false,如果查找到第一个满足条件的元素,就会终止循环
forEach和some的区别
1、在forEach里面,return不会终止迭代
2、
let arr = [1,2,4,'pink','green',7,11,22,33];
arr.some(function (value){
if (value === 'green'){
console.log('找到了该元素');
return true; //some中return true会终止迭代,效率更高
}
console.log(value)
});
arr.forEach(function (value){
if (value === 'green'){
console.log('找到了该元素');
return true; // forEach中return不会终止迭代
}
console.log(value);
})
trim方法:去除字符串两端的空格,但是不能去除字符串中的空格
object.defineProperty
:定义新属性或修改原有的属性
object.defineProperty(obj, prop, descriptor)
接收三个参数
obj
:必传。目标对象
prop
:必传。需要定义或修改的属性的名字
descriptor
:必传。目标属性所拥有的特性
descriptor说明:
该参数需要以对象形式书写
1、函数声明方式function关键字(命名函数)
function fn() {}
2、函数表达式(匿名函数)
let fn = function () {}
3、new Function(‘参数1’, ‘参数2’, ‘函数体’)
Function里面参数都必须是字符串格式
这种方法执行效率低,不方便书写,较少使用
let f = new Function ('console.log('123')') {} //不传参情况
4、所有函数都是 Function的实例(对象)
函数也属于对象,因为有原型__proto__
// 1、普通函数
function fn() {
console.log('nb666');
}
fn();
fn.call();
// 2、对象的方法
let o = {
name:'ckw',
say: function (){
console.log('我是o对象的say方法');
}
}
o.say();
// 3、构造函数
function Star() {}
new Star();
// 4、绑定事件函数
btn.onclick = function (){
console.log('123123')
}
// 5、定时器函数
setInterval(function (){},1000); //每1秒执行一次
// 6、立即执行函数
(function (){
console.log(123);
})()
// 立即执行函数会自动调用
1、call()方法
fun.call(thisArg, arg1, arg2, ...)
call()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的this指向
let o = {
name: 'ckw'
};
function fn(a, b) {
console.log(this);
console.log(a+b)
}
fn.call(o,1,2);
function Father(uname, age, sex){
this.uname = uname;
this.age = age;
this.sex = sex;
}
function Son(uname, age, sex){
Father.call(this,uname, age, sex);
}
let son = new Son('ckw',18,'男');
console.log(son);
call方法:第一个可以调用函数,第二个可以改变函数内的this指向。也可以实现继承
2、apply()方法
// 2、apply()方法
let o = {
name: 'andy'
};
function fn(arr){
console.log(this);
console.log(arr); // 此处只输出第一个pink
}
fn.apply(o, ['pink','blue','aqua']);
// 1、apply()方法既可以调用函数,也可以修改函数内部的this指向
// 2、但是它的第二个参数必须是数组/伪数组
// 3、apply()的主要应用,比如说可以利用 apply 借助Math内置对象求最大值
let arr = [1,0,3,8,9,77];
let arr1 = ['red', 'aqua']
let res = Math.max.apply(null,arr);
let res1 = Math.max.apply(null,arr1); // 如果传入的是字符串数组,则会输出NaN
console.log(res);
console.log(res1);
值得注意的是,当apply方法的第二个参数传入的是一个有多个元素的数组时,它只会输出一个元素。
3、bind()方法
bind()方法不会调用函数,但是能改变函数内部this指向
fun.bind(thisArg, arg1, arg2, ...)
相同点:都可以改变函数内部的this指向
不同点:
call
是用arg1,arg2, ...
的方式传递,apply
是用[arg1,arg2, ...]
的方式传递主要应用场景
JS除了提供普通模式以外,还提供了严格模式(strict mode),ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格模式下运行JS代码。
严格模式对正常的JS语义做了一些更改
除了JavaScript语法的一些不合理,不严谨之处,减少了一些怪异行为。
消除代码运行的一些不安全之处,保证代码运行的安全。
提高编译器效率,增加运行效率
禁用了ECMAScript的未来版本中可能会定义的一些语法,为未来的JS版本做好铺垫。比如一些保留字:
enum,class,export,extends,import等不能做变量名
严格模式中的函数不能有重名参数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或者将函数作为返回值输出
function fn(a, b, callback) {
console.log(a + b);
callback && callback();
}
fn(1,2,function (){alert('我是最后调用的')});
function fn1() {
return function (){};
}
fn1();
上面两种函数都属于高阶函数
变量根据作用域的不同可以分为:全局变量或局部变量。
闭包指有权访问另一个函数作用域中变量的函数
即一个作用域可以访问另一个函数内部的局部变量
// 闭包:fun这个函数访问到了fn函数作用域中的局部变量num,此时就产生了闭包
// fn外面的作用域也可以访问fn内部的局部变量num
// 闭包的主要作用:延伸了变量的作用范围。
function fn() {
let num = 10;
// function fun() {
// console.log(num);
// }
return function () {
// 此时这个函数内并没有Num变量,但是依旧可以正常打印,这就产生了闭包
console.log(num);
};
}
let f = fn();
f();
// 类似于
// f = function fun() {
// console.log(num);
// }
闭包是一个函数(一个作用域可以访问另一个作用域的局部变量)
延伸了变量的作用范围
如果一个函数可以在内部调用其本身,那么这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow ),所以必须要加退出条件return
function fn(n){
if (n === 1){
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
console.log(fn(4));
// 详细思路
// 第一次执行,return 3*fn(2)
// 第二次执行,return 3*( 2 * fn(2 - 1) )
// 第三次执行,return 3*( 2 * 1 ),最后输出结果为6
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(6));
// 输入n = 6
// 第一次 return fb(5)+fb(4)
// 第二次 return fb(4)+fb(3) + return fb(3)+fb(2)
// 第三次 return fb(3)+fb(2) + return fb(2)+fb(1) + return fb(2)+fb(1) + return 1
// 第四次 return fb(2)+fb(1) + return 1 + return 1+1 + return 1+1 + return 1
// return 1+1 + return 1 + return 1+1 + return 1+1 + return 1;
// 输出结果为8
let data = [
{
id: 1,
name: '家电',
goods: [
{
id: 11,
gname: '洗衣机'
},
{
id: 22,
gname: '冰箱',
goods: [
{
id: 111,
name: '海尔'
},
{
id: 112,
name: '西门子'
}
]
}
]
},
{
id: 2,
name: '服饰'
}
]
// 需要输入id就返回name
// 利用forEach()遍历数组中的每一个对象
function getId(json, id) {
let obj = {}
json.forEach(item => {
// console.log(item);
if (item.id === id) {
// console.log(item);
obj = item;
// 想要里层的id:11/id:12的数据,可以利用递归函数
// 里面应该有goods这个数组,且数组不为空
} else if (item.goods && item.goods.length !== 0) {
// 再调用一次getId
obj = getId(item.goods, id)
}
});
return obj;
}
console.log(getId(data, 1));
console.log(getId(data, 22));
console.log(getId(data, 112));
1.浅拷贝只是拷贝一层,更深层次对象级别的只会拷贝引用(地址)
let obj = {
id: 1,
name: 'zs',
msg: {
age: 18
}
};
let o = {};
// for (let k in obj){
// k是属性名,obj[k]是属性值
// o[k] = obj[k];
// }
// console.log(o);
// o.msg.age = 20;
// console.log(obj);
// 用ES6语法糖:Object.assign()来实现浅拷贝
Object.assign(o, obj);
console.log(o);
o.msg.age = 20;
console.log(obj);
也可以用ES6语法糖Object.assign()
来实现浅拷贝,但是修改age的结果与上面这种方法相同
2.深拷贝会拷贝多层,每一层级别的数据都会拷贝
递归方式实现深拷贝
let obj = {
id: 1,
name: 'zs',
msg: {
age: 18
},
color: ['red', 'blue']
};
let o = {};
// 封装函数
// newobj[k]是key,item是value
function deepCopy(newobj, oldobj) {
for (let k in oldobj) {
// 判断属性值是哪种数据类型
// 1.获取属性值 oldobj[k]
let item = oldobj[k];
// 2.判断这个值是否是数组
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item)
} else if (item instanceof Object) {
// 3.判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k], item)
} else {
// 4.属于简单数据类型
newobj[k] = item
}
}
}
deepCopy(o, obj);
console.log(o);
正则表达式是用于匹配字符串中字符组合的模式,在JS中,正则表达式也是对象。正则表达式通常被用来检索、替换那些符合某 个模式/规则的文本,例如表单验证等等。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等。
1.灵活性、逻辑性和功能性非常强
2.可以迅速的用极简单的方式达到字符串的复杂控制
3.对初学者来说比较晦涩难懂
4.实际开发中,一般是直接复制写好的正则表达式,但是需要会使用并且根据实际情况修改。
1.通过RegExp
对象的构造函数创建
// 利用RegExp对象来创建正则表达式
let regexp = new RegExp('/123/');
console.log(regexp);
// 利用字面量创建正则表达式
let reg = /123/;
// test方法用来检测字符串是否符合正则表达式要求的规范
console.log(reg.test(123));
console.log(reg.test('abc'));
一个正则表达式可以由简单的字符构成,例如/abc/
,也可以是简单和特殊字符的组合,例如/ab*c/
。其中,特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如^、$、+
等
let rg = /abc/; //正则表达式里面不需要加引号,无论数字型或字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test('abcc')); // true
console.log('---------');
let reg = /^abc/;
// /^abc/ 表示以必须以abc开头
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); //false
console.log('---------');
let reg1 = /^abc$/;
// /^abc$/ 表示精确匹配,要求必须是abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false
[]
字符类:表示有一系列字符可供选择,只要匹配其中一项就可以(多选一)
let rg = /[abc]/ //只要包含a、b、c中的任何一项,则返回true
console.log(rg.test('aa1q123')); //true
console.log(rg.test('zsdf3123')); //false
console.log('----------');
let rg1 = /^[abc]$/ //三选一,只有是a/b/c才返回true
console.log(rg1.test('ab')); //false
console.log(rg1.test('a')); //true
console.log(rg1.test('b')); //true
console.log(rg1.test('c')); //true
console.log('----------');
let reg = /^[a-z]$/ //26个英文字母中的任意一个都返回true
console.log(reg.test('a')); //true
console.log(reg.test('b')); //true
console.log(reg.test('c')); //true
console.log(reg.test('A')); //false
console.log(reg.test(1)); //false
console.log('------------');
// 字符组合
let reg1 = /^[a-zA-Z0-9_·-]$/ //大小写的英文字母和数字都可以
console.log(reg1.test('a')); //true
console.log(reg1.test('B')); //true
console.log(reg1.test('1')); //true
console.log(reg1.test('_')); //true
console.log(reg1.test('-')); //true
console.log(reg1.test('·')); //true
console.log(reg1.test('-_')); //false
console.log('--------------------');
// 如果中括号中有'^'则代表取反,需要与边界符分割开理解
let reg2 = /^[^a-zA-Z0-9_·-]$/
console.log(reg2.test('!')); // true
// 量词符:用来设定某个模式出现的次数
// 简单理解:就是让下面的/^a$/字符重复多少次
// let reg = /^a$/
// * 代表 >= 0 代表可以出现0次和很多次
let reg = /^a*$/
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aa')); // true
console.log('----------------');
// + 代表 >=1 代表可以出现1次和很多次
let reg1 = /^a+$/
console.log(reg1.test('')); // false
console.log(reg1.test('a')); // true
console.log(reg1.test('aa')); // true
console.log('-----------------');
// ? 相当于 1 || 0
let reg2 = /^a?$/
console.log(reg2.test('')); // true
console.log(reg2.test('a')); // true
console.log(reg2.test('aaaa')); // false
console.log('-------------');
// {n}重复n次
let reg3 = /^a{3}$/
console.log(reg3.test('')); // false
console.log(reg3.test('a')); // false
console.log(reg3.test('aaaa')); // false
console.log(reg3.test('aaa')); // true
console.log('----------------');
// {n,}大于等于n次
let reg4 = /^a{3,}$/
console.log(reg4.test('')); // false
console.log(reg4.test('a')); // false
console.log(reg4.test('aaaa')); // true
console.log(reg4.test('aaa')); // true
console.log('--------');
// {n,m}大于等于 n 次,小于等于 m 次
let reg5 = /^a{3,5}$/
console.log(reg5.test('')); // false
console.log(reg5.test('a')); // false
console.log(reg5.test('aaaa')); // true
console.log(reg5.test('aaa')); // true
console.log(reg5.test('aaaaa')); // true
console.log(reg5.test('aaaaaaa')); // false
用量词符实现简单的表单验证
<input type="text">
<button>提交button>
<div id="warn">div>
let inputVal = document.getElementsByTagName("input");
let btn = document.getElementsByTagName("button");
let warn = document.getElementById('warn');
let reg = /^[a-zA-z0-9-_]{6,16}$/;
console.log(inputVal);
inputVal[0].onblur = function (){
// console.log(reg.test(this.value));
warn.innerHTML = '';
if (reg.test(this.value)){
inputVal[0].style.borderColor = 'aquamarine';
} else {
warn.innerHTML = '请输入正确的用户名';
}
}
1.replace正则替换
let str = 'andy';
let newstr = str.replace(/n/, '*');
console.log(newstr); // a*dy
2.正则表达式参数:
/表达式/[switch]
switch参数:
g:全局匹配
i:忽略大小写
gi:全局匹配+忽略大小写
通过var
声明的变量在定义语句之间就可以访问到,值为undefined
function
声明的函数,在声明之前就可以调用,值:函数定义(对象)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsnuNGTp-1663658730051)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220920094733229.png)]
函数执行上下文只在调用的时候产生,不调用不产生
隔离变量:不同作用域的同名变量不会冲突