原型
1.原型上存放函数
- 解决了同一个
say浪费 内存的问题 - 解决了污染全局变量的问题
function createStudent(name, age) {
this.name = name;
this.age = age;
}
// 将刚才的全局函数say 直接挂载到 构造函数的原型上 即可
// prototype 是个对象 每一个构造函数都会内置有的. 我们称之为原型
createStudent.prototype.say = function () {
console.log(this.name);
}
const obj = new createStudent("悟能", 83);
const obj1 = new createStudent("悟能1", 84);
console.log(obj.say === obj1.say); // true
2.原型解释
- 原型的单词是
prototype, 原型的这个名字是行业内共同认可的名字。 - 原型本质是一个对象,理解为
JavaScript自动帮我们添加的,只要是构造函数,系统会默认的为构造函数关联一个对象,这个对象就称为构造函数的原型,写在原型中的成员,可以被构造函数所创建的实例调用 - 原型是
JavaScript自动帮我们在定义构造函数的时候添加的 - 所有构造函数的实例,共享一个原型
- 原型上一般是挂载函数
3.构造函数的原型
<script>
// 原型是什么:系统会为构造函数默认关联一个对象,这个对象就是构造函数的原型
// 原型如何操作:
// 与之前的对象的操作方式一样:添加成员,删除成员,遍历
// 通过 构造函数.prototype 访问原型对象
// 原型谁可以使用,如何使用:只要这个构造函数所创建的对象都是访问该构造函数的原型
function Student(name, age) {
// 添加属性
this.username = name
this.userage = age
}
// 为确知函数的原型添加成员
Student.prototype.say = function() {
console.log(this.username)
}
let obj = new Student('jack', 20)
console.log(obj)
let obj2 = new Student('rose', 20)
console.log(obj2)
// 通过构造函数创建的对象要吧访问原型中的成员
console.log(obj.say === obj2.say)
obj.say()
</script>
原型的关系
构造函数、实例、原型三者之间的关系
构造函数:构造函数就是一个函数,配合new可以新建对象。
实例:通过构造函数实例化出来的对象我们把它叫做构造函数的实例。一个构造函数可以有很多实例。
原型:每一个构造函数都有一个属性prototype,函数的prototype属性值就是原型。通过构造函数创建出来的实例能够直接使用原型上的属性和方法。
所有的构造函数都是Function的实例
Array 和 Person 和 Date 等都是 Function的实例
Function 和 Object的关系
有人说
JavaScript是作者花了7天时间写出来的产物 - 不完美
console.log(Object.prototype===Function.prototype.__proto__)
Object的顶端呢?
接近顶峰了
console.log(Object.prototype.__proto__ === null);
5.原型链
1概念
任何一个对象,都有原型对象,原型对象本身又是一个对象,所以原型对象也有自己的原型对象,这样一环扣一环就形成了一个链式结构,我们把这个链式结构称为:原型链。
- 总结:Object.prototype是原型链的尽头,Object.prototype的原型是null。
完整原型三角关系
属性查找原则
如果是获取操作
- 会先在自身上查找,如果没有
- 则根据
__proto__对应的原型去找,如果没有 - 一直找到
Object.prototype,如果没有,那就找不到从而报错
5.继承
<script>
function Person(name, age) {
// 添加属性
this.username = name
this.userage = age
}
// 为确知函数的原型添加成员
Person.prototype.say = function() {
console.log(this.username)
}
function Student(name, age, gender) {
this.username = name
this.userage = age
this.usergender = gender
}
// 缺点:繁琐
// Student.prototype.say = Person.prototype.say
// Student.prototype.hello = Person.prototype.hello
// 缺点
// 1.会将Student构造函数的原型进行覆盖,覆盖掉之前的内容
// 2.修改对象的构造函数的指向
Student.prototype = Person.prototype
Student.prototype.constructor = Student
// 实现继承:获取到Person构造函数原型中的成员添加到自身
let stu = new Student('rose', 20, '女')
let stu1 = new Student('tom', 20, '男')
stu.say()
stu1.say()
console.log(stu.__proto__.constructor)
let per = new Person()
console.log(per.__proto__.constructor)
</script>
6.原型继承-一个细节
<script>
function Person(name, age) {
// 添加属性
this.username = name
this.userage = age
}
// 为确知函数的原型添加成员
Person.prototype.say = function() {
console.log(this.username)
}
function Student(name, age, gender) {
this.username = name
this.userage = age
this.usergender = gender
}
// 重置原型对象实现继承
Student.prototype = Person.prototype
Student.prototype.constructor = Student
// 实现继承:获取到Person构造函数原型中的成员添加到自身
// 一个对象所指向的原型是:创建这个对象时,构造函数所指向的原型
let stu = new Student('rose', 20, '女')
stu.say()
</script>
7.继承另外一种实现方式
<script>
function Person(name, age) {
// 添加属性
this.username = name
this.userage = age
}
// 为确知函数的原型添加成员
Person.prototype.say = function() {
console.log('Person的', this.username)
}
Person.prototype.hello = function() {
console.log(this.age)
}
function Student(name, age, gender) {
this.username = name
this.userage = age
this.usergender = gender
}
Student.prototype.say = function() {
console.log('Student的', this.username)
}
// 原型是一个对象
for (key in Person.prototype) {
// 判断 自身是否有同名键,如果有则跳过,没有才添加
if (!Student.prototype[key]) {
// if (!Student.prototype.hasOwnProperty(key)) {
Student.prototype[key] = Person.prototype[key]
}
}
let stu = new Student('rose', 20, '女')
let stu1 = new Student('tom', 20, '男')
stu.say()
stu1.say()
console.log(stu.__proto__.constructor)
let per = new Person()
console.log(per.__proto__.constructor)
</script>
二、作用域及作用域链
1.作用域的基本表现
let所创建的变量的作用域是从创建这个let变量到它所在的结构的}结束
作用域:变量起作用的区域,也就是说:变量定义后,可以在哪个范围内使用该变量。
<script>
let num = 11 //全局变量
function fn() {
let num1 = 22 //局部变量,只有方法体内可以使用
console.log(num) // 函数内部可以使用函数外部声明的变量,全局变量在任何地方都能访问到
console.log(num1)
}
fn()
console.log(num)
</script>
2.词法作用域
在js里只有全局作用域和函数作用域。
函数作用域是在函数定义的时候作用域就确定下来了,和函数在哪调用无关。
-
我们只关注函数的定义位置而不关注函数的调用位置
- 定义函数,函数的作用域就确定了
- 以后函数做为参数,不影响函数的作用域
<script>
// 词法作用域:函数一旦声明,作用域就确定好了
// 函数的作用域只是声明有关,与调用无关,函数不关注在什么位置调用,只关注在什么位置声明
var num = 123
function f1() {
console.log(num) // 123
}
function f2() {
var num = 456
f1()
}
f2()
</script>
3. var,let,const的区别
<script>
// 能使用const就使用const
// 不能使用const,就使用let
// 不要使用var
// let 的使用
// 会有块级作用域
// 块就是指 { }
// 作用域就是声明这个let变量的{}
// 有效作用域:从let开始到它所在的}结束
// 不会进行声明的提升,一定要先声明赋值再使用
// if (1) {
// // console.log(age) // Cannot access 'age' before initialization
// let age = 20
// console.log(age)
// }
// console.log(age) // age is not defined
// var:
// 没有块级作用域的概念
// 会进行声明的提升,将声明提升到当前作用域的最前面
// var age
// if (1) {
// console.log(age)
// age = 20
// console.log(age)
// }
// console.log(age)
// const:定义常量:不能修改的变量:一般在模块中的成员都是常量
// 1.基本特性和let一样
// 2.声明赋值之后不能再修改
// 3.只能在声明的同时进行赋值
// if (1) {
// console.log(age)
// const age = 20
// }
// 常量名称一般全部大写
const PI // Missing丢失 initializer初始操作 in在 const常量 declaration声明
PI = 3.14
// PI = 3.15// Assignment赋值 to给 constant常量 variable变量
console.log(pi)
</script>
4. var在业务处理中的细节
<body>
<p>第1个p元素</p>
<p>第2个p元素</p>
<p>第3个p元素</p>
<p>第4个p元素</p>
<script>
let ps = document.querySelectorAll('p')
for (let i = 0; i < ps.length; i++) {
// 同步任务
ps[i].addEventListener('click', function() {
// 异步任务
ps[i].style.color = 'red'
})
console.log(i)
}
</script>
</body>
5. 作用域的练习
<script>
// var num = 10
// fn1()
// function fn1() {
// var num
// // var会将声明提升到当前作用域(全局作用域,函数作用域)的最前面
// console.log(num) // undefined
// // 自己的作用域有则使用自己的,否则 往外找
// num = 20
// console.log(num) // 20
// }
// // 函数 外部不能直接访问函数内部所声明的成员
// console.log(num) // 10
// var num = 10
// fn1()
// function fn1() {
// // 自己的作用有域没有,则找外部作用域
// console.log(num) // 10
// // 自己的作用有域没有,则找外部作用域,修改了全局变量
// num = 20
// console.log(num) // 20
// }
// console.log(num) // 20
// var num = 123
// // num是一个形参,相当于方法的局部变量
// // 函数的作用域只与函数的声明位置有关,与调用位置无关
// function f1(num) {
// // 先使用自己的作用域的num
// console.log(num) // 456
// }
// function f2() {
// var num = 456
// f1(num)
// }
// f2()
// var num1 = 10
// var num2 = 20
// function fn(num1) {
// var num3 // 提升到函数作用域的最前面
// num1 = 100 // 修改=的是形参,方法的局部变量,与全局的没有关系
// num2 = 200 // 自己没有num2,所以查找外层作用域,修改的是全局变量num2
// num3 = 300 // 修改了自己的num3
// console.log(num1) // 100
// console.log(num2) // 200
// console.log(num3) // 300
// }
// fn()
// console.log(num1) // 10
// console.log(num2) // 200
// console.log(num3) // 报错
</script>
this与函数的四种调用模式
根据函数内部this的指向不同,可以将函数的调用模式分成4种
- 函数调用模式
- 方法调用模式
- 构造函数调用模式
- 上下文调用模式(借用方法模式)
函数调用模式
如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window
function fn(){
console.log(this);// 指向window
}
fn();
方法调用模式
当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象
const obj = {
sayHi:function(){
console.log(this);//在方法调用模式中,this指向调用当前方法的对象。
}
}
obj.sayHi();
构造函数调用模式
如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上。
function Person(){
console.log(this);
}
Person();//this指向什么?
var p = new Person();//this指向什么?
调用模式真题
<script>
// var声明的变量会挂载到全局window
// let age = 38
// var obj = {
// age: 18,
// getAge: function() {
// console.log(this)
// console.log(this.age) // 38
// }
// }
// var f = obj.getAge
// f() // 函数调用模式,说法中的this指向window
// var age = 38
// var obj = {
// age: 18,
// getAge: function() {
// console.log(this.age) // 18
// function foo() {
// console.log(this.age) // 38
// }
// foo() // 函数调用模式 -- window
// }
// }
// obj.getAge() // 方法调用模式 -- obj
var length = 10
function fn() {
console.log(this.length) // 10 3
}
var obj = {
length: 5,
method: function(fn) {
fn() // 函数调用模式,this指向window
// obj.a >> obj['a']
arguments[0]() // 方法调用模式 arguments.0()
// js的参数是通过内置的Arguments进行接收的,它是一个伪数组(对象)
console.log(arguments)
}
}
obj.method(fn, 10, 5, 123, 12, 323, 4)
</script>
继承构造函数
<script>
function Person(name, age) {
this.username = name // 房子
this.userage = age // 车子
}
// 为确知函数的原型添加成员
Person.prototype.say = function() {
console.log(this.username)
}
function Student(name, age, gender) {
// this.username = name // 房子
// this.userage = age // 车子
// 让父类构造函数帮我构建这两个成员
// Person(name, age)// 函数调用模式,this指向window
Person.call(this, name, age)
this.usergender = gender // 你自己
}
// 继承原型中的所有成员
Student.prototype = Person.prototype
Student.prototype.constructor = Student
// 实现继承:获取到Person构造函数原型中的成员添加到自身
let stu = new Student('rose', 20, '女')
console.log(stu)
console.log(window)
// stu.say()
</script>
方法借用模式-上下文调用模式
也叫上下文模式,分为 apply 与 call,bind
call
call方法可以调用一个函数,并且可以指定这个函数的this指向
<script>
let obj = {
username: 'jack',
say: function(age) {
console.log(this, `我的年龄是:${age}`)
}
}
obj.say(20) // this >> obj
let newobj = {
name: 'rose'
}
// // 使用call实现修改方法中的this的指向
// // call是方法的方法
// // 方法.call(this对象,参数....),让方法中this的指向从默认值改变为你所传入的对象
// // console.dir(obj.say)
obj.say.call(newobj, 30)
</script>
call的运用
将伪数组转成数组
let divs = document.querySelectorAll('div'); // 伪数组
// let divs = document.body.children;
console.log(divs);
function change(nodelist) {
console.log(Object.prototype.toString.call(nodelist));
return Array.prototype.slice.call(nodelist);
}
自定义一个push方法
<script>
// 伪数组:本质是一个对象
let fackArr = {
0: 1,
1: 3,
2: 5,
3: 19,
length: 4
}
for (let i = 0; i < fackArr.length; i++) {
console.log(fackArr[i])
}
// 我想向伪数组中添加一个成员
// fackArr.push(100)
// Array.prototype.push.call(fackArr, 200)
;[].push.call(fackArr,100)
console.log(fackArr)
</script>
自定义一call方法
<script>
Function.prototype.mycall123 = function(newobj) {
// 正常情况下,如果想让一个方法中的this指向某个对象,最简单的方式是:某个对象.方法()
// 谁调用方法,谁就是方法里面的this,现在是say在调用mycall123方法,所以mycall123方法中的this就是say
console.log(this, typeof this, '-----')
// this() == say()
newobj.fn = this
newobj.fn() // this >> newobj
delete newobj.fn
}
let obj = {
username: 'jack',
say: function(age = 30) {
console.log(this, `我的年龄是:${age}`)
}
}
let newobj = {
name: 'rose'
}
// 让say方法中的this从obj变成newobj
obj.say.mycall123(newobj)
// obj.say() // this >> obj
// newobj.say() // this >> newobj
// console.log(obj.say.__proto__.constructor) // Function
console.log(newobj)
</script>
apply
就是apply()方法接受的是一个包含多个参数的数组。而call()方法接受的是若干个参数的列表
可以利用apply 将 刚才的call 的代码修改一下
const RichWumon = {
name: "富婆",
say: function () {
console.log(this.name, " 我要重金求子");
}
}
const obj = {
name: "屌丝"
}
RichWumon.say(); // 富婆
RichWumon.say.apply(obj); // 屌丝
apply应用
1.简化log方法
// 简化log方法
function log() {
// 不需要改变this
console.log.apply(console, arguments);
}
bind方法
bind() 方法创建一个新的函数, 可以绑定新的函数的this指向
var name = '张三';
function Fn(){
this.age = 1;
console.log(this.name + this.age);
}
Fn(); // 张三 1
// 返回值:新的函数
// 参数:新函数的this指向,当绑定了新函数的this指向后,无论使用何种调用模式,this都不会改变。
let obj = {
name:'小强',
}
const newFn = Fn.bind(obj);
newFn(); // 小强 1
this的指向
-
单独使用,
this指向全局对象console.log(this); -
函数中的
this指向全局对象function show(){ console.log(this); } show(); -
在函数内部,
this的指向在函数定义的时候是不能确定的,只有函数执行的时候才能确定const a = 18; const obj = { a: 19, b: { a: 20, c: function () { console.log(this.a); // 20 } } } obj.b.c(); -
在方法中,
this指代该调用方法的对象
const obj ={
name:"小白",
say:function(){
console.log(this);
}
}
obj.say()
箭头函数
箭头函数-语法
<script>
// 语法
// function test(a, b) {
// let sum = a + b
// return sum
// }
// 1.箭头函数是匿名函数,所以不能单独存在
// 2.一般用于 函数表达式,参数
// let test = (a, b) => {
// let sum = a + b
// return sum
// }
// 如果箭头函数体只有一句,可以省略{},同时会返回函数体的结果(我们不用再写return)
// 如果你自己添加了{},如果需要返回值则需要手动return
// let test = (a, b) => a + b
// 如果只有一个 参数,则可以省略(),否则 一定需要()
let test = a => a + 10
// 调用方法:和之前一样
let sum = test(100, 20)
console.log(sum)
</script>
箭头函数-特性
- 箭头函数的this是确定的,况且永远不变
- 箭头函数中的this指向 创建这个箭头函数所在对象 的上下文
<script>
let obj = {
name: 'jack',
say: function() {
return () => {
// 箭头函数的this:指向 创建这个箭头函数 所在对象 的上下文
console.log(this)
}
}
}
let fn = obj.say()
// fn()
let newobj = {
name: 'rose'
}
newobj.fn12 = fn
newobj.fn12()
// fn.call(newobj)
// obj.say() 方法调用模式 》》 window
// let fn = obj.say
// fn() // 函数调用模式 》》 window
// let newobj = {
// name:'rose'
// }
// obj.say.call(newobj) // 上下文调用模式 》》 window
</script>