typora-root-url: images
typora-copy-images-to: images
原型对象的再认识
构造函数
function Person(){
}
let p1 = new Person();
console.log(p1);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WvaYlRn-1663214187825)(/1649409880014.png)]
数组的原型对象
原型对象上的各种方法,让我们声明的数组,可以随便调用
prototype原型继承
所有函数只要创建出来,系统都会分配一个原型对象给整个函数,通过prototype找到原型对象.
我们创建的每个函数都有一个 prototype
(原型)属性。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。
定义:
1 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法.原型也是对象.
2 利用原型的概念和特点可以提取共有属性.
3 实例化对象可通过_ _proto__查看原型
原型与对象
概念1讲解
function Person(){
}
// Person.prototype 原型 祖先
Person.prototype.age = 18;
Person.prototype.say = function(){
console.log('吃饭了');
}
//Person.prototype.age = 22;
let p1 = new Person();
let p2 = new Person();
console.log(p1);
概念2讲解 ,提取构造函数中的公共属性
第一步:
function Cart(ower,color){
this.name="BMW";
this.lang = '3米'
this.price = 30000;
this.ower = ower;
this.color = color;
}
let p1 = new Cart();
let p2 = new Cart();
console.log(p1);
console.log(p2);
console.log(p1===p2);
此时可以看出构造函数的多次创建会产生多个相同函数,造成冗余太多。
利用原型prototype解决。回顾prototype
console.log(Cart.prototype);
//constructor表示当前的函数属于谁
//__proto__ == [[prototype]],书面用语,表示原型指针
第二步:直接使用原型提取公共属性
// Cart.prototype.name = "BMW";
// Cart.prototype.lang = "3米";
// Cart.prototype.price = 30000;
Cart.prototype={
name:'BMW',
lang:'3m',
price:30000
}
function Cart(ower,color){
this.ower = ower;
this.color = color;
}
let p1 = new Cart();
let p2 = new Cart();
console.log(p1);
console.log(p2);
概念3讲解 ,实例化的对象用__proto__访问原型
function Cart(own, color) {
this.name = 'BMW';
this.lang = '3米';
this.price = 300000;
this.own = own;
this.color = color;
}
// 构造函数查看原型 prototype
console.log(Cart.prototype)
//实例化对象查看原型 __proto__
var c1 = new Cart('ls', 'red');
console.log(c1.__proto__)
原型链继承
JavaScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.
简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针(proto)
原型链的连接点就是 __ proto __
从近的往远的访问.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-66ply0Zw-1663214187827)(/1563327865157.png)]
_ proto _ 是实例对象,拥有对的指向原型的属性,而构造函数是没有的.
function Person(name){
this.name = name;
this.age = 18;
}
var zs = new Person('zs');
// console.log(Fn.prototype.constructor);
console.log(zs.__proto__); // {constructor: ƒ} Fn的原型
console.log(zs.__proto__.__proto__.constructor); //Object
console.log(zs.__proto__.__proto__.__proto__); //null
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5xkKtR2S-1663214187828)(/1567236728534.png)]
绿色的线表示原型链,当zs实例调用一个属性或者方法时,现在自身找,然后一层一层的向上,直至找到null.没有则返回undefined
图中的Objct,是构造方法
原型链的改造
<script>
Grand.prototype.lastName = 'cheng';
function Grand(){
}
var grondObj = new Grand();
Father.prototype = grondObj;
function Father(){
this.name="zs"
}
var fatherObj = new Father();
Son.prototype = fatherObj;
function Son(){
this.hobbit = 'smoke';
}
var sonObj = new Son();
</script>
当Son要访问一个属性不存在时,就会找到原型的指针__proto__,然后找到原型继续找
实例属性与原型属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJ9wAgkz-1663214187828)(/1563327459646.png)]
原型属性的操作(了解)
查,即获取数据
删,子孙不能删除,其本身可以删除
改,除非自己增,后辈无法增
增,自己增,后辈无法增
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WA30IkH-1663214187829)(/1563265424339.png)]
更多的原型对象
class
class也是有原型对象的.
class Person{}
class Test{}
class Student extends Person{
}
console.log(Person.prototype);
Array的原型
数组的原型上有很多方法,所以新生命的数组就可以额直接调用
字符串的原型对象
原型对象的意义
1 为了方便我们修改原型对象,实现继承.
2 可以手动给原型对象上,添加方法,实例化对象都可以使用
3 可以重构原型对象的方法,实现我们想要的功能.-
call/apply继承
作用:都是改变this的指向,只是传参的形式不同
call方法
作用: 调用该函数,并修改函数中this的指向
**语法: ** 函数名. call(对象,实参1,实参2);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
后面的参数: 被调用函数要传入的实参,以逗号分隔
<script>
function Person(name,age){
this.name = name;
this.age = age;
console.log(this);
}
var personObj = new Person('cheng',88);
let obj = {}
Person.call(obj,'feng',66);
</script>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9AqDhrMO-1663214187831)(/1563266567780.png)]
例1:编写person和sudent两个函数,让student中的属性和person的一样,通过call改变调用方法
第一步:
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,tel,grade){
this.name = name;
this.age = age;
this.sex = sex;
this.tel = tel;
this.grade = grade;
}
var student = new Student();
思考:变量出现了重复怎么才能在Student中直接调用Person中的属性
第二步:在Student中调用call,传递this
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
console.log(this);
}
function Student(name,age,sex,tel,grade){
Person.call(this,name,age,sex,);
this.tel = tel;
this.grade = grade;
}
var student = new Student('cheng',20,'男',18336629253,'A');
注意:this的指向
apply 方法
作用: 调用该函数,并修改函数中this的指向
**语法: ** 函数名. apply(对象,数组);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
第二个参数: 要去传入一个数组,里面存放被调用函数需要的实参
function fn(x, y){
console.log(this); // {a : 1}
console.log(x + y); //8
}
fn.apply({a : 1}, [3, 5]);
bind方法
作用: 不调用函数,克隆一个新的函数,并修改新函数中this的指向,将新的函数返回
**语法: ** 函数名. bind(对象[,实参]);
参数详解:
第一个参数: 要让函数中this指向谁,就写谁
后面的参数: 被调用函数要传入的实参,以逗号分隔
例1:实现fn中this指向的改变
function fn(x, y){
//调用newFn时打印结果
console.log(this); //{b:2}
console.log(x + y); //8
}
var newFn = fn.bind({b:2}, 3, 5);
newFn();
例2:给每一个li注册一个点击事件,点击这个li之后,每隔一秒钟,打印一下当前li中的文本
// 1.1 获取元素
var lis = document.getElementsByTagName('li');
// 1.2 给每一个li注册点击事件
for(var i = 0 ; i < lis.length; i++){
lis[i].onclick = fn;
}
//每一个li的事件处理函数
function fn(){
// console.log(this);// --> 点击的那个li
// 1.3 在事件处理函数中,设置一个定时器
setInterval(function(){
// 1.4 在定时器的回调函数中,打印当前li的文本
console.log(this.innerText);
// console.log(this); //-->window
}.bind(this), 1000);
// setInterval(function(){
// // 1.4 在定时器的回调函数中,打印当前li的文本
// console.log(this.innerText);
// // console.log(this); //-->window
// }.call(this), 1000);
}
小结:
1 当我们需要直接调用函数且修改this的指向,使用call或者apply
2 当我们需要修改this的指向,但是不直接调用函数,让js来调用时,使用bind.例如,延时器,定时器,事件函数
继承
构造函数继承
我们工作中要经常创建多个具有相同属性的对象,所以经常要写构造函数.
那么构造函数创建的出来的对象该如何实现继承呢?
例:使用call构造函数的继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(name, age, score){
this.score = score;
Person.call(this, name, age);//借用构造函数
}
var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs
原型继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
function Student(score){
this.score = score;
}
Student.prototype = new Person();
var stu = new Student(100);
console.log(stu); //{score : 100}
stu.sayHello(); //hello, 我是undefined
实例化的时候没有传值,所以才会出现这种情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzjYlsFp-1663214187832)(/1563269665581.png)]
我们发现,方法继承下来了,但是属性却没有继承下来
混合继承
借用构造函数 + 原型继承
function Person(name, age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log('hello, ' + '我是' + this.name );
}
}
//父类的原型对象属性
Person.prototype.id = 10;
function Student(name, age, score){
this.score = score;
Person.call(this, name, age); //借用构造函数,继承父类模板
}
Student.prototype = new Person();
var stu = new Student('zs', 18, 100);
console.log(stu.id); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs
闭包
什么是闭包
在函数外部能够读取其他函数内部变量的函数。
通俗理解的闭包: 一个内部函数引用了外部函数的变量,外部函数形成了一个闭包.
闭包认识
初体验
例:在函数的外部实现函数内部变量的访问
function fun1(){
var n=999;
function fn2(){
console.log(n);
}
return fn2; //fn2 就是一个闭包函数,因为他能够访问到fun1函数的作用域
}
var r=fun1();
r();
原理
js中的变量,函数在执行的时候都会被加载到栈中,执行完毕在弹出,当使用闭包的时候,函数执行完成后,不会弹出,因为其他的地方还要加载其内部变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hnlw9oM0-1663214187833)(/1591502134979.png)]
闭包的特点
1 使用不当会很容易造成内存泄露(内存中能存的东西越来越少,像是其他部分被泄露了一样)
2 设置私有变量(内部函数调用外部函数的局部变量,此时,这个局部变量就会变成内部函数的私有变量)
3 闭包的作用:充当一个摄像头,函数外部可以访问函数内部的变量,减少变量的声明,避免造成污染.
4 内存的占用比较大,浪费内存.
函数柯理化
是什么
只是一种闭包的应用
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
怎么用
验证密码是否符合要求
方法1:正常写法
function check(reg, str) {
return reg.test(str)
}
// 验证密码
const res = check(/^\w{6,12}$/, 'asdhgfahsd')
const res2 = check(/^\w{6,12}$/, 'sdfsdf')
方法2:利用闭包
function fn(reg) {
return function inner(str) {
return reg.test(str)
}
}
const nameTest = fn(/^\w{5,11}$/)
const r3 = nameTest('asd')
console.log(r3);
上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。
函数的节流和防抖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpY160I3-1663214187834)(/1649162927143.png)]
以前的做法
<input type="text" value="" id="user">
<script>
let input = document.getElementById('user');
user.addEventListener('input', function () {
console.log(this.value);
});
以输入框为例,我将给input绑定input事件,当键盘松开则在控制台打印此时input框内的内容。
防抖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q9CoNHaz-1663214187835)(/1649162983732.png)]
在事件触发指定时间之后才会执行相应的处理,若在这指定时间内事件又被调用,则会重新计时执行。
所以如果事件一直在指定时间内连续的执行,则不会触发相应处理。直到一次指定时间内没有事件被执行,才会执行一次相应的处理。
const anti_shake = function () {
let timer = null;
return function (val) {
if (timer) { // 如果存在定时器,则清除上一次操作的定时器
clearInterval(timer);
}
timer = setTimeout(() => { // 重新开启定时器
console.log(val);
}, 500);
}
}
const ctrl = anti_shake();
document.getElementById("user").addEventListener('input', function (e) {
ctrl(e.target.value);
});
节流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nTpCKDj9-1663214187836)(/1649163046718.png)]
`事件触发后在指定时间后执行相应的事件处理,如果在这个指定时间内再次触发事件,事件不会被执行。`
直到上一次事件处理完成后,才会执行下一次的事件处理。如果一直触发事件,则事件的处理会很规律的被执行。
const throttling = function () {
let lock = false; // 定义锁
return function (val) {
if (lock) {
return;
}
lock = true; // 开启锁,阻止其他事件
// 执行操作
console.log(val);
setTimeout(() => {
lock = false; // 500ms后开锁
}, 500);
}
}
const ctrl = throttling();
document.getElementById("user").addEventListener('input', function (e) {
ctrl(e.target.value);
});
一直输出的话就会每隔一段时间处理相应的操作。
面试题
使用原生js给每个li绑定onclick事件,点击后输出当前li对应的索引值
一般方式
function test(){
var liObj = document.getElementsByTagName('li');
var len = liObj.length;
for(var i=0;i<len;i++){
liObj[i].onclick = function(){
console.log(i);
}
}
}
test();
闭包写法1:
<script>
var liObj = document.getElementsByTagName('li');
function test(){
var len = liObj.length;
for(var i=0;i<len;i++){
addClick(i);
}
}
function addClick(j){
liObj[j].onclick = function(){
console.log(j);
}
}
test();
</script>
多学一招:自定义匿名函数
(function(){})是一个标准的函数定义,但是没有复制给任何变量。所以是没有名字的函数,叫匿名函数。没有名字就无法像普通函数那样随时随地调用了,所以在他定义完成后就马上调用他,后面的括号()是运行这个函数的意思
扩展
set和get
getter可以得到一些东西的方法,setter可以设置东西
class Person {
constructor() {
this._name = '';
}
// 获取值的
get name() {
console.log("正在访问name");
return `我的名字是${this._name}`;
}
// 设置值的
set name(val) {
console.log("正在修改name");
this._name = val;
}
}
const person = new Person();
person.name = "歌王";
console.log(person.name); //先访问set 再访问get
nclick = function(){
console.log(j);
}
}
test();
多学一招:自定义匿名函数
(function(){})是一个标准的函数定义,但是没有复制给任何变量。所以是没有名字的函数,叫匿名函数。没有名字就无法像普通函数那样随时随地调用了,所以在他定义完成后就马上调用他,后面的括号()是运行这个函数的意思
### 扩展
#### set和get
getter可以得到一些东西的方法,setter可以设置东西
class Person {
constructor() {
this._name = ‘’;
}
// 获取值的
get name() {
console.log(“正在访问name”);
return 我的名字是${this._name}
;
}
// 设置值的
set name(val) {
console.log(“正在修改name”);
this._name = val;
}
}
const person = new Person();
person.name = “歌王”;
console.log(person.name); //先访问set 再访问get