js学习之路(六)
-
-
- 1. this 指向
- 2. 改变 this 指向
- 3. ES6 定义变量
- 4. 04_变量的块级作用域
- 5. ES6 的箭头函数
- 6. 箭头函数的特性
- 7. 函数的参数默认值
- 8.模板字符串
- 9.点点点(...) 运算符
- 10.解构赋值
- 11.对象的简写
- 12.了解面向对象开发
- 13.创建对象的四种方式
- 14.构造函数的书写和使用
- 15.构造函数是否合理
- 17.prototype
- 18.__proto__
1. this 指向
/*
this 指向
+ 定义:
=> this 是一个使用再作用域内部的关键字
=> 全局很少用, 大部分是在函数内部使用
+ 指向:
=> 全局使用: window
=> 函数使用: **不管函数怎么定义, 不管函数在哪定义, 只看函数的调用(箭头函数除外)**
-> 普通调用(直接调用/全局调用)
+ 函数名(): this -> window
-> 对象调用
+ xxx.函数名(): this -> 点前面是谁就是谁
-> 定时器处理函数
+ setTimeout(function () {}, 0): this -> window
+ setInterval(function () {}, 0): this -> window
-> 事件处理函数
+ xxx.onclick = function () {}: this: 事件源(绑定再谁身上的事件)
+ xxx.addEventListener('', function () {}): this: 事件源
-> 自执行函数
+ (function () {})(): this -> window
*/
// function fn() {
// console.log(this)
// }
// fn() // 普通调用 this -> window
// var obj = {
// // 把 fn 存储的地址赋值给了 obj 的 f 成员
// // 从现在开始 obj.f 和 全局变量 fn 指向同一个函数空间
// f: fn,
// name: '我是 obj 对象'
// }
// obj.f() // 对象调用 this -> obj
// // 把 fn 函数当作定时器处理函数使用
// setTimeout(fn, 0) // 定时器处理函数 this -> window
// setTimeout(obj.f, 0) // 定时器处理函数 this -> window
// var div = document.querySelector('div')
// // 当点击 div 的时候, 执行 obj.f 这个函数
// // div.onclick = obj.f // 事件处理函数 this -> 事件源
// div.addEventListener('click', obj.f) // 事件处理函数 this -> 事件源
// function fn() {
// console.log(this)
// }
// fn() // window
// setTimeout(function () {
// fn() // window
// }, 0)
// var div = document.querySelector('div')
// div.onclick = function () {
// function f() {
// console.log(this)
// }
// f() // window
// }
// var obj = {
// name: '我是 obj 对象',
// fn: function () {
// console.log(this)
// function fun() {
// console.log(this)
// }
// fun() // window
// }
// }
// obj.fn() // this -> obj
// 把 obj 里面 fn 成员存储的函数地址赋值给了全局变量 f
// 全局变量 f 和 obj.fn 指向同一个函数空间
// var f = obj.fn
// f() // this -> window
2. 改变 this 指向
/*
改变 this 指向
+ this 有他本身的指向性
+ 不管你本身指向哪里, 我让你指向谁, 你就指向谁
+ 三个方法
1. call()
2. apply()
3. bind()
1. call()
+ 使用方法, 就直接连接再函数名后面使用
+ 语法:
-> fn.call()
-> obj.fn.call()
+ 参数:
-> 第一个参数, 就是函数内部的 this 指向
-> 第二个参数开始, 依次给函数传递参数
+ 特点:
-> 会立即执行函数(不适合用作定时器处理函数或者事件处理函数)
+ 作用:
-> 伪数组借用数组方法
2. apply()
+ 使用方法, 就直接连接再函数名后面使用
+ 语法:
-> fn.apply()
-> obj.fn.apply()
+ 参数:
-> 第一个参数, 就是函数内部的 this 指向
-> 第二个参数: 是一个数组或者伪数组都行, 里面的每一项依次给函数传递参数
+ 特点:
-> 会立即执行函数
+ 作用: 可以以数组的形式给某些功能函数传参
-> Math.max()
3. bind()
+ 使用方法, 就直接连接再函数名后面使用
+ 语法:
-> fn.apply()
-> obj.fn.apply()
+ 参数:
-> 第一个参数. 就是函数内部的 this 指向
-> 从第二个参数开始, 依次给函数传递参数
+ 特点:
-> 不会立即调用函数
-> 会返回一个新的函数, 一个已经被改变好 this 指向的函数
+ 作用:
-> 改变事件处理函数或者定时器处理函数的 this 指向
*/
function fn(a, b) {
console.group('fn 函数')
console.log(this)
console.log(a)
console.log(b)
console.groupEnd()
}
var obj = {
name: '我是 obj 对象'
}
// fn(10, 20) // this -> window
// 1. call()
// 使用 call 方法去调用 fn 函数, 把函数内部的 this 指向改变成 obj
// fn.call(obj, 100, 200)
// 2. apply()
// 使用 apply 方法调用 fn 函数, 把函数内部的 this 指向改变成 obj
// fn.apply(obj, [1000, 'world'])
// 3. bind()
// 使用 bind 方法改变 fn 函数的 this 指向
// res 是一个 fn 函数的克隆版, 只不过里面的 this 被锁死了, 指向 obj
// var res = fn.bind(obj, 'hello', 'world')
// res()
/*
小例子
*/
// var arr = [100, 200, 33, 56, -200, -300, 72]
// // var res = Math.max(100, 200, 33, 56, -200, -300, 72)
// var res = Math.max.apply(null, arr)
// console.log(res)
/*
小例子
*/
// function f() {
// console.log(arguments)
// 伪数组用不了数组常用方法
// 但是数组可以使用
// every() 方法的调用, 需要接收一个函数作为实参
// arguments.every() 因为 arguments 没有 every 方法, 所以报错
// var res = arguments.every(function (t) { return t >= 20 })
// 数组再调用 every 方法
// every 里面的 this 指向前面的数组
// var res = [].every()
// 利用 call 方法来执行 数组的 every() 函数
// var res = [].every.call()
// 第一个参数是 every 里面的 this 指向
// 原先 every 的 this 指向 数组的时候, 遍历查看数组
// 现在 evert 的 this 指向 arguments, 遍历查看 arguments
// var res = [].every.call(arguments)
// call 的第二个参数是给函数传递参数
// 函数 a 就是传递给 every 方法里面的参数函数
// var res = [].every.call(arguments, function a(item) { return item >= 8 })
// console.log(res)
// }
// f(10, 20, 30, 40, 50)
/*
小例子
*/
// var div = document.querySelector('div')
// function handler(a, b) {
// console.log(this)
// console.log(a, b)
// }
// var obj = { name: '我是 obj 对象' }
// call 不合适, 因为还没等到点击呢, 函数就执行了
// div.onclick = handler.call(obj)
// div.onclick = handler.apply(obj)
// 不会把 handler 函数执行, 而是返回一个新的函数, 一个已经锁死 this 指向的函数
// var res = handler.bind(obj)
// 调用的是一个被 bind 锁死 this 指向的函数
// div.onclick = handler.bind(obj, 100, 200)
3. ES6 定义变量
ES6:
+ 官方名称叫做 ES2015
+ 语法层面的更新
=> 原先: var a
=> ES6: let a
+ 我们书写代码:
=> 不需要考虑语法层面的兼容
=> ES6 转换 ES5 的工具叫做 babel
=> https://www.babeljs.cn/ -> 试一试 -> 你在左边框书写 ES6 语法
ES6 定义变量
+ ES6 确认了两个定义变量的关键字
1. let : 变量
2. const : 常量
let/const 一起和 var 的区别
1. var 会进行预解析
=> let/const 不会进行预解析, 必须先定义后使用
2. var 可以声明重复变量名
=> let/const 不能声明重复的变量名
3. var 没有块级作用域
=> let/const 有块级作用域
let 和 const 的区别
1. let 叫做变量
=> const 叫做常量
2. let 可以再声明的时候不进行赋值
=> const 再声明的时候必须进行赋值
3. let 生命的变量可以被修改
=> const 声明的常量不能被修改, 一旦修改就报错
变量的定义规范
1. 尽量使用 let 和 const 定义
2. 书写代码的时候尽可能优先使用 const
块级作用域
+ 被代码块限制变量的使用方法
+ var: 只有函数私有作用域才能限制使用范围
+ let/const: 只要是能书写代码段的 {} 都能限制使用范围
*/
// let 和 const 的区别
// 1. 声明不赋值
// let num
// console.log(num)
// const 声明不赋值会报错
// const num2
// console.log(num2)
// 2. 不允许被修改
// let num = 100
// console.log(num)
// num = 200
// console.log(num)
// const 不允许被修改
// const num2 = 100
// console.log(num2)
// num2 = 200
// const obj = {
// name: 'Jack'
// }
// console.log(obj.name)
// obj.name = 'Rose'
// console.log(obj.name)
// let/const 和 var 的区别
// 1. 预解析
// console.log(a)
// var a = 100
// console.log(a)
// 使用再定义之前会报错
// console.log(a)
// let a = 100
// console.log(a)
// console.log(a)
// const a = 100
// console.log(a)
// 2. 重复变量名
// var num = 100
// var num = 200
// console.log(num)
// 不能重名
// let num = 100
// console.log(num)
// let num = 200
// const num = 200
// console.log(num)
// const num = 300
// 3. 块级作用域
// if (true) {
// // 全局变量
// var num = 100
// }
// console.log(num)
// let 定义的 num 离开这个 if 的 {} 就用不了了
// if (true) {
// let num = 200
// console.log(num)
// }
// let num = 300
// console.log(num)
4. 04_变量的块级作用域
Document
5. ES6 的箭头函数
/*
ES6 的箭头函数
+ 一种新的函数定义方式
+ 对于函数表达式的简写方式(匿名函数)
+ 匿名函数
=> var fn = function () {}
=> var obj = { fn: function () {} }
=> setTimeout(function () {}, 0)
=> setinterval(function () {}, 0)
=> [].forEach(function () {})
=> div.onclick = function () {}
=> div.addEventListener('click', function () {})
=> ...
+ 语法: () => {}
=> (): 形参的位置
=> =>: 箭头函数的标志
=> {}: 代码段
*/
var fn = function () { console.log('我是一个函数') }
fn()
var fun = (a, b) => {
console.log('我是一个 fun 函数')
console.log(a)
console.log(b)
}
fun(100, 200)
6. 箭头函数的特性
/*
箭头函数的特性
1. 箭头函数如果只有一个形参
=> 那么可以省略小括号不写
=> (a) => {}
-> a => {}
2. 箭头函数代码段里面只有一句话, 可以省略大括号不写
=> 并且会自动 return 这一句话的结果
=> () => { return 123 }
=> () => 123
3. 箭头函数里面没有 arguments 这个东西
=> 压根没有, 你用不了
4. 箭头函数里面没有 this 关键字
=> 官方: 箭头函数里面的 this 是 上下文(context), 外部作用域的 this 就是箭头函数内的 this
=> 私人: 箭头函数的 this 是, 你的箭头函数写在哪一行, 上一行的 this 就是箭头函数里面的 this
5. 箭头函数里面的 this 任何方法改变不了
=> 因为箭头函数没有 this
=> call / apply / bind 不能改变 箭头函数的 this 指向
*/
// 1. 一个形参可以省略小括号不写
// let fn = (a) => { console.log(a) }
// let fun = a => { console.log(a) }
// fn(100)
// fun(200)
// 2. 一句话可以省略大括号不写, 并且自动 return
// let fn = (a, b) => { return a + b }
// console.log(fn(10, 20))
// // 把 a + b 的结果当作 fun 的返回值
// var fun = (a, b) => a + b
// console.log(fun(100, 200))
// let arr = [10, 20, 30, 40, 50]
// let res = arr.every(function (item) { return item >= 10 })
// let res2 = arr.every(item => { return item >= 10 })
// let res3 = arr.every(item => item >= 10)
// console.log(res, res2, res3)
// 3. 箭头函数里面没有 arguments
// let fn = () => { console.log(arguments) }
// let fun = function () { console.log(arguments) }
// fun(100, 200, 300)
// fn(10, 20, 30)
// 4. 箭头函数里面没有 this
// let div = document.querySelector('div')
// div.onclick = function () {
// console.log(this)
// }
// div.onclick = () => {
// console.log(this) // window
// }
// div.onclick = function () {
// let fn = function () {
// console.log(this)
// }
// fn() // window
// let fun = () => { // 就是 84 行的 this
// console.log(this)
// }
// fun() // div
// }
// let obj = {
// name: '我是 obj 对象',
// fn: function () { console.log(this) },
// fun: () => { console.log(this) }
// }
// obj.fn() // obj
// obj.fun() // window
// let div = document.querySelector('div')
// div.onclick = function () {
// // this
// let obj = {
// name: '我是 obj 对象',
// fn: function () { console.log(this) },
// fun: () => { console.log(this) }
// }
// obj.fn() // obj
// obj.fun() // div
// }
// 5. 箭头函数改变不了 this 指向
// let fn = () => { console.log(this) }
// fn() // window
// let obj = { name: 'Jack' }
// fn.call(obj)
7. 函数的参数默认值
/*
函数的参数默认值
+ 给函数的形参设置一个默认值
=> 如果你传递了实参, 就使用你传递
=> 如果你没有传递实参, 那么就使用默认值
+ 直接再形参后面使用 等于号(=) 进行赋值
*/
// 1. 普通函数的参数默认值
function fn(a = 100, b = 200) {
// 形参 a 的默认值是 100
console.log(a)
console.log(b)
}
fn()
fn(10)
fn(10, 20)
// 箭头函数也可以书写参数默认值
let fun = (a = 1000, b = 2000) => {
console.log(a, b)
}
fun()
fun(100)
fun(100, 200)
// 箭头函数只要你设置参数默认值, 不管多少个形参, 都得写小括号
// let f = a = 100 => {} // 报错的
let f = (a = 100) => {}
8.模板字符串
/*
模板字符串
+ ES6 定义了一种声明字符串的方式
+ 使用 反引号(``)
+ 特点:
1. 可以换行书写
2. 可以直接进行变量的拼接
3. 模板字符串可以调用函数
=> 字符串里面的内容是函数的参数
=> ${} 把字符串切开, 组合成一个数组当作第一个参数
=> 从左到右开始依次是每一个 ${} 里面的内容作为函数后面的参数
*/
// 1. 换行书写
// let str = `
// 123
// 456
// `
// 2. 拼接变量
// let age = 18
// let str2 = `
// 我今年 ${ age } 岁了
// `
// console.log(str2)
// function fn(a, b, c) {
// console.log(a)
// console.log(b)
// console.log(c)
// }
// var num = 100
// var num2 = 200
// fn`hello ${ num } world ${ num2 } 你好`
// 1. ${} 切开字符串 ['hello ', ' world ', ' 你好']
// 2. ${ num } 里面得 num 就是函数的第二个参数
// 3. ${ num2 } 里面的 num2 就是函数的第三个参数
9.点点点(…) 运算符
/*
点点点(...) 运算符
+ 展开运算符
=> 当你再函数的实参位置或者数组或者对象里面使用的时候是 展开
=> 就是把包裹的内容全部打开
+ 合并运算符
=> 当你再函数的形参位置使用的时候是 合并
=> 作用: 箭头函数没有 arguments, 我们使用 合并运算符整一个
*/
// 1. 展开
// let arr1 = [1, 2, 3, 4]
// console.log(...arr1)
// console.log(Math.max(...arr1))
// let arr2 = [...arr1, 5, 6, 7, 8]
// console.log(arr2)
// let obj = {
// name: 'Jack',
// age: 18
// }
// let obj2 = {
// ...obj,
// gender: '男'
// }
// console.log(obj, obj2)
// 2. 合并
// function fn(...a) {
// // 定义一个 a 变量, 从第一个实参开始到最后一个实参全部获取, 放在一个数组里面
// console.log(a)
// }
// fn(100, 200, 300, 400, 500, 600)
// function fn(a, ...b) {
// // 定义一个 a 变量, 接收第一个实参
// // 定义一个变量 b, 从第二个开始到最后一个实参放在一个数组里面
// console.log(a)
// console.log(b)
// }
// fn(100, 200, 300, 400, 500, 600)
// function fn(a, b, ...c) {
// // 定义一个 a 变量, 接收第一个实参
// // 定义一个 b 变量, 接收第二个实参
// // 定义一个变量 c, 从第三个开始到最后一个实参放在一个数组里面
// console.log(a)
// console.log(b)
// console.log(c)
// }
// fn(100, 200, 300, 400, 500, 600)
let fn = (...arg) => {
console.log(arg)
}
fn(10, 20, 30, 40, 50)
10.解构赋值
/*
解构赋值
+ 定义: 快速从 对象 或者 数组 里面获取一些数据
+ 分成两种
1. 解构数组
=> 语法: let [变量1, 变量2, ...] = [数据1, 数据2, ...]
=> 也可以解构多维数组
2. 解构对象
=> 语法: let { key1, key2, ... } = { 键值对1, 键值对2, ... }
=> 解构的时候可以给解构的变量起一个别名
-> { name: 别名 } = {}
=> 也可以解构多维对象
*/
// 2. 解构对象
// let obj = { name: 'Jack', age: 18, gender: '男' }
// let name = obj.name
// let age = obj.age
// let gender = obj.gender
// console.log(name, age, gender)
// 解构赋值
// let { name, age, gender } = obj
// console.log(name, age, gender)
// 解构起一个别名
// let n = obj.name
// let { name: n, age: a, gender } = obj // => let n = obj.name
// console.log(n, a, gender)
// 解构多维对象
let o1 = {
a: 100,
b: 200,
o2: {
c: 300,
o3: {
d: 400
}
}
}
// console.log(o1)
// let d = o1.o2.o3.d
// let c = o1.o2.c
// let b = o1.b
// let a = o1.a
// console.log(a, b, c, d)
// 解构赋值
// let { a, b, o2: { c, o3: { d } } } = o1
// console.log(a, b, c, d)
// let { c, o3: { d } } = o1.o2
// let c = o1.o2.c
// let { d } = o1.o2.o3
// let d = o1.o2.o3.d
// 1. 解构数组
// let arr = [10, 20, 30, 40]
// 获取数组中的索引 [0]
// let a = arr[0]
// let b = arr[1]
// let c = arr[2]
// let d = arr[3]
// console.log(a, b, c, d)
// 解构赋值获取数组里面的成员
// let [a, b, c, d] = arr
// console.log(a, b, c, d)
// 解构多维数组
// let arr = [10, 20, [30, 40, [50]]]
// let a = arr[0]
// let b = arr[1]
// let c = arr[2][0]
// let d = arr[2][1]
// let e = arr[2][2][0]
// console.log(a, b, c, d, e)
// 解构赋值的形式
// let [a, b, [c, d, [e]]] = arr
// console.log(a, b, c, d, e)
// 交换变量
// var a = 5
// var b = 6
// console.log(a, b)
// var [b, a] = [a, b]
// console.log(a, b)
11.对象的简写
/*
对象的简写:
+ 再 ES6 标准下, 有一个对象的简写方式
1. 当 key 和 value 一模一样的时候, 可以只写一个
2. 当 某一个 key 的值是一个函数的时候, 并且不是箭头函数
=> 可以直接省略 function 关键字和 冒号 不写
*/
// 1. key 个 value 一样, 可以省略一个不写
// let age = 18
// let obj = {
// name: 'Jakc',
// age, // 等价于 age : age
// gender: '男' // 不能省略
// }
// console.log(obj)
// 2. 函数省略
// f1 和 f3 是一回事
let obj = {
name: '我是 obj 对象',
f1: function () { console.log(this) },
f2: () => { console.log(this) },
f3 () { console.log(this) }
}
obj.f1() // obj
obj.f2() // window
obj.f3() // obj
12.了解面向对象开发
/*
了解面向对象开发
+ 是一个我们的开发思想(你写代码的方式)
+ 面向过程
=> 再开发的过程中, 关注每一个 步骤 细节 顺序, 实现效果
+ 面向对象
=> 再开发的过程中, 只关注有没有一个对象能帮我完成
例子: 我今天要吃面条
+ 面向过程
1. 和面 - 多少面粉 多少水
2. 切面 - 多宽 多细
3. 煮面 - 多长时间
4. 拌面 - 多少酱 多少面
5. 吃面 - 一口吃多少
+ 面向对象
1. 找一个面馆
2. 点一碗面
3. 等着吃
+ 面向对象: 对面向过程的高度封装(高内聚低耦合)
在开发过程中
+ 面向过程
=> 按照顺序一步一步来
+ 面向对象(轮播图)
=> 找到一个对象, 能帮我完成轮播图
=> JS 本身没有, 我们需要第三方
=> swiper: 生成一个完成轮播图的对象
+ 我们:
=> 当你需要完成一个 功能A 的时候
=> 我们找到 JS 有没有这个完成 功能A 的对象
=> 如果没有, 我们 制造一个 "机器"
=> 这个 "机器" 可以制造完成 功能A 的对象
"机器" 是什么
+ 能力: 能创造一个 有属性 有方法 合理的 对象
+ 构造函数就是这个 "机器"
模拟: 选项卡
+ 面向过程
1. btns: [按钮1, 按钮2, 按钮3]
2. tabs: [盒子1, 盒子2, 盒子3]
3. 事件: 让 btns 里面的成员添加点击事件, 操作 btns 和 tabs 里面的每一个
+ 抽象成对象
o = {
btns: [按钮1, 按钮2, 按钮3],
tabs: [盒子1, 盒子2, 盒子3],
方法: function () {
给 o.btns 里面的每一个绑定事件
操作 o.btns 和 o.tabs 里面的每一个操作类名
}
}
+ 面向对象
=> 书写一个构造函数
=> 能创建一个对象包含三个成员
1. btns
2. tabs
3. 方法, 能操作自己的btns 和tabs 的方法
=> 使用这个构造函数创建一个对象, 根据你传递参数来实现选项卡效果
*/
13.创建对象的四种方式
/*
创建对象的四种方式
*/
/*
字面量创建
=> var obj = {}
*/
// let o1 = {
// name: 'Jack',
// age: 18,
// gender: '男'
// }
// 当我需要第二个对象的时候
// let o2 = {
// name: 'Rose',
// age: 20,
// gender: '女'
// }
/*
内置构造函数创建
=> var obj = new Object()
*/
// let o1 = new Object()
// o1.name = 'Jack'
// o1.age = 18
// 当我想创建第二个对象的时候
// let o2 = new Object()
// o2.name = 'Rose'
// o2.age = 20
/*
工厂函数创建对象
1. 先自己做一个工厂函数
2. 使用自己做的工厂函数来创建对象
*/
// 1. 创建一个工厂函数
// function createObj(name, age, gender) {
// // 1-1. 手动创建一个对象
// let obj = {}
// // 1-2. 手动添加成员
// obj.name = name
// obj.age = age
// obj.gender = gender
// // 1-3. 手动返回这个对象
// return obj
// }
// 2. 使用工厂函数创建对象
// 创建第一个对象
// let o1 = createObj('Jack', 18, '男')
// console.log(o1)
// 创建第二个对象
// let o2 = createObj('Rose', 20, '女')
// console.log(o2)
/*
自定义构造函数创建
1. 自己书写一个构造函数
2. 使用构造函数创建对象
构造函数
+ 就是普通函数, 没有任何区别
+ 只有在你调用的时候和 new 关键字连用, 才有构造函数的能力
=> 只要你和 new 关键字连用 this => 当前对象(new 前面的那个变量名)
*/
// 1. 创建一个构造函数
function createObj(name, age, gender) {
// 1-1. 自动创建一个对象
// 1-2. 手动向对象上添加内容
this.name = name
this.age = age
this.gender = gender
// 1-3. 自动返回这个对象
}
// 2. 创建对象
let o1 = new createObj('Jack', 18, '男') // 本次调用的时候, 函数内部的 this 就指向 o1
console.log(o1)
let o2 = new createObj('Rose', 20, '女')
console.log(o2)
14.构造函数的书写和使用
/*
构造函数的书写和使用
+ 明确: 构造函数也是函数, 只不过是在调用的时候和 new 关键字连用了
+ 目的: 就是为了创建一个 有属性 有方法 合理的 对象
1. 调用必须有 new 关键字
=> 如果没有, 那么没有创建对象的能力
=> 只要有, 就会自动创建一个对象
2. 在构造函数内部不要写 return
=> 如果 return 基本数据类型, 写了白写
=> 如果 return 复杂数据类型, 构造函数白写
3. 构造函数在调用的时候, 如果不需要传递参数, 最后的小括号可以不写
=> 但是推荐我们都写上
4. 构造函数推荐首字母大写
=> 是为了直观看出和普通函数的区别
=> 看到首字母大写的函数, 基本上就要和 new 连用
5. 当函数和 new 关键字连用
=> 会创造对象, 我们关创造出来的对象叫做 实例对象
=> 我们关创造的过程叫做 实例化 的过程
=> 构造函数体内的 this 指向当前实例对象
=> 也就是本次 new 的时候创建的那个对象
*/
// 1. 必须和 new 关键字连用
// function fn() {}
// let o1 = new fn()
// console.log(o1)
// let o2 = fn()
// console.log(o2)
// 2. 不要写 return
// function fn() {
// this.a = 100
// 基本数据类型写了白写
// return 123
// 复杂数据类型, 构造函数白写
// return new Date()
// }
// let o1 = new fn()
// console.log(o1)
// 3. 不传递参数, () 可以不写
// function fn() {
// this.a = 100
// this.b = 200
// }
// let o1 = new fn()
// console.log(o1)
// let o2 = new fn
// console.log(o2)
// 4. 首字母大写
// function Fn() {
// this.a = 100
// this.b = 200
// this.c = 300
// }
// function fun() {
// var a = 100
// var b = 200
// return a + b
// }
function Person(name) {
this.name = name
this.sayHi = function () { console.log('hello world') }
}
// 本次 new 的时候, Person 会创造一个对象
// 把创造出来的对象赋值给了 变量 p1
// 我们管 p1 叫做 Person 的实例化对象
// 本次调用的时候, Perosn 函数内部的 this 就指向 p1
let p1 = new Person('张三')
// 本次 new 的时候, Perosn 创造第二个对象
// 赋值给了 p2 变量
// p2 也是 Perosn 的实例化对象
// 本次调用的时候, Person 函数内部的 this 就指向 p2
let p2 = new Person('李四')
console.log(p1, p2)
p1.sayHi()
p2.sayHi()
15.构造函数是否合理
/*
构造函数是否合理
+ 一旦在构造函数体内书写方法的时候
+ 你创建多少个实例化对象, 那么就有多少个方法要占用内存空间
+ 不合理: 因为会有对于的函数内存空间被占用
*/
// function Person(name) {
// this.name = name
// this.sayHi = function a() { console.log('你好 世界') }
// }
// let p1 = new Person('张三') // 本次创建的时候, 会开辟一段内存空间存储 a 函数
// let p2 = new Person('李四') // 本次创建的时候, 会开辟一段内存空间存储 a 函数
// 函数单独提取出来没有问题
// 但是每一个函数占用一个变量
// function a() { console.log('你好 世界') }
// function b() { console.log('你好 世界') }
// function Person(name) {
// this.name = name
// this.sayHi = a
// this.sayHi2 = b
// }
// let p1 = new Person('张三')
// let p2 = new Person('李四')
// 把所有的方法放在一个对象里面
// 我要是有 一百个 构造函数, 就要有 一百个 对象
let PerosnObj = {
a: function a() { console.log('你好 世界') },
b: function b() { console.log('你好 世界') }
}
function Person(name) {
this.name = name
this.sayHi = PersonObj.a
this.sayHi2 = PersonObj.b
}
let p1 = new Person('张三')
let p2 = new Person('李四')
17.prototype
/*
prototype (原型 / 原型对象)
+ 定义: **每一个函数天生自带一个属性叫做 prototype, 他是一个对象**
+ 只要函数定义好以后, 这个 prototype 就出生了
+ 构造函数也是函数, 构造函数也有 prototype, 我们可以像里面添加一些内容
+ 这个天生自带的 prototype 里面有一个属性叫做 constructor
=> 表示我是哪一个构造函数伴生的原型对象
*/
function Person() {}
// Person.prototype 已经出生了
// 像 prototype 上添加一些属性和方法
Person.prototype.sayHi = function () { console.log('hello world') }
console.log(Person.prototype)
18.proto
/*
__proto__
+ 定义: **每一个对象天生自带一个属性, 叫做 __proto__, 指向所属构造函数的 prototype**
+ 实例化对象也是一个对象
+ 实例化对象也有 __proto__ 属性
*/
function Person() {}
// Person 出生的时候就带着 prototype
// 像里面添加 一些内容
Person.prototype.sayHi = function () { console.log('hello world') }
// 因为 P1 是 Person 实例化出来的对象
// 所以 p1 所属的构造函数就是 Person
let p1 = new Person()
console.log(p1)
// 当我访问 p1.__proto__ 的时候就应该由 sayHi
console.log('Person.prototype', Person.prototype)
console.log('p1.__proto__', p1.__proto__)
console.log(Person.prototype === p1.__proto__) // true