目录
1. JS语言特性
- 跨平台与可移植性:
跨平台,与操作系统无关。
支持多种运行环境(服务器端Node.js、客户端浏览器、移动/桌面应用)。
- 弱类型:
弱类型,变量类型运行时确定,无需预声明,提供高度灵活性。
- 解释执行:
解释性脚本语言,无需预编译,由JS引擎(V8、SpiderMonkey)逐行解释执行。
开发调试快,但执行效率可能低于编译型语言。
- 动态类型:
运行时类型推断,根据变量当前值自动维护类型。支持自动类型转换。
- 灵活特性:
函数可像其他数据类型一样传递、赋值。
支持闭包,允许函数访问并操作外部函数变量。
原型继承机制,实现基于原型的对象继承。
- 编程范式:
支持面向对象编程(OOP),通过原型链实现继承。
支持函数式编程(FP),利用高阶函数、闭包等特性。
- 异步编程:
异步处理不阻塞主线程,如网络请求、文件读写等。
引入Promise、async/await,简化异步编程,提高代码可读性和可维护性。
- 标准库与生态系统:
标准库丰富,提供内置对象和方法。
生态系统庞大,数千个三方库和框架(React、Vue、Angular),扩展JS 功能和应用范围。
2. JS数据类型
2.1 数据类型
基本数据类型
- 类型:String, Number, BigInt, Boolean, null, undefined, Symbol
- 存储:栈内存,按值访问
- 特性:
- 不可变:值一旦创建,不可改变(但变量可重新指向新值)
- 操作:直接操作实际值
- 比较:基于值的比较
- 赋值:简单赋值,复制值到新变量位置,改变的是变量的指针指向
引用数据类型
- 主要类型:Object, Array, RegExp, Date, Function,特殊的基本包装类型(String, Number, Boolean的复杂形式),单体内置对象(Global, Math)
- 存储:
- 栈内存:存储引用地址
- 堆内存:存储实际对象
- 特性:
- 可变:可以改变对象的属性和方法
- 赋值:传递对象引用,即引用地址
- 比较:基于引用地址的比较
- 操作:通过引用地址操作实际对象
2.2 Number
NaN
定义:NaN(Not-a-Number)是Number类型的特殊值,代表非数字或无法计算的数值。
特性
- 不等于任何值,包括自身(
NaN !== NaN
为true
)。 - 在布尔逻辑中视为
false
。 - 与任何数值运算均产生NaN。
- 运算错误或无法得出有效数值时返回NaN,如
0 / 0
或Math.sqrt(-1)
。
检测
isNaN()
:尝试转换参数为数字,对非数字字符串也返回true
。Number.isNaN()
:不尝试转换参数,仅当参数确实为NaN时返回true
。
其他关键点
- 无穷大:JS中的Number类型包含正无穷大(
Infinity
)和负无穷大(-Infinity
),表示超出数值表示范围的极限值。 - 精度问题:由于JS使用IEEE 754标准的64位双精度浮点数表示Number,高精度计算时可能遭遇精度丢失。对于高精度需求,推荐使用如
big.js
、decimal.js
等库。 - 类型转换:JS会自动转换类型,特别是在数学运算中,非Number值会被尝试转换为Number。
2.3 字符串
// 去除首尾空格
str.trimStart();
str.trimLeft();
str.trimEnd();
str.trimRight();
str.trim()
str.replace(/^\s*|\s*$/g,'')
// startwith()判断当前字符串是否以另外一个给定的子字符串开头,返回 true 或 false。
str.startsWith(searchString[, position])
// indexof()返回指定子字符串在字符串中第一次出现的索引,没有则为-1
str.indexOf(searchValue [, fromIndex])
//字符串转数字
parseInt(string, radix) // 解析字符串string并返回指定基数radix(2-36 )的十进制整数
Number(str)
2.4 null和undefined
null:表示“没有对象”或“空引用”,用于显式地表示一个变量不指向任何对象。
undefined:表示“缺少值”或“未定义”,常见以下场景:
- 声明变量未赋值,包含解构赋值没有定义的变量。
- 函数默认返回值。
- 访问对象不存在的属性。
- 函数实参少于形参时,未指定的参数默认为undefined。
- 空值合并运算符
??
左侧操作数为null或undefined时
空值合并运算符 '??'
||
返回第一个 真 值,无法区分false
、0
、空字符串""
和null/undefined
( 假值)
??
返回第一个 已定义的 值,只想在变量的值为null/undefined
时使用默认值
console.log(undefined == null); // true
console.log(undefined === null); // false
console.log(undefined + 5); // NaN
console.log(null + 5); // 5
console.log('123' + 5); // 1235
console.log('123' * 1 + 5); // 128
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
2.5 Symbol
Symbol:ES6新增,用于创建唯一标识符。使用相同名称创建也会生成不同值,确保唯一性。
用途:防止对象属性名冲突,确保属性名唯一。
示例:let id = Symbol("id");
这里的id
是独一无二的Symbol值。
相关方法
- Object.getOwnPropertySymbols(obj):获取对象
obj
上所有Symbol属性组成的数组。 - Reflect.ownKeys(obj):返回对象
obj
的所有键名,包括常规属性和Symbol属性。
2.6 数组
常用方法
push()
:向数组末尾添加一个或多个元素,并返回新的长度,改变原数组。
语法: array.push(element1, ..., elementN)
示例: let arr = [1, 2]; arr.push(3, 4); // arr 变为 [1, 2, 3, 4]
pop()
:删除并返回数组的最后一个元素,改变原数组。
语法: array.pop()
示例: let arr = [1, 2, 3]; let last = arr.pop(); // arr 变为 [1, 2], last 为 3
shift()
:删除并返回数组的第一个元素,改变原数组。
语法: array.shift()
示例: let arr = [1, 2, 3]; let first = arr.shift(); // arr 变为 [2, 3], first 为 1
unshift()
:向数组的开头添加一个或多个元素,并返回新的长度,改变原数组。
语法: array.unshift(element1, ..., elementN)
示例: let arr = [2, 3]; arr.unshift(1, 0); // arr 变为 [1, 0, 2, 3]
splice()
:通过删除现有元素或添加新元素来更改数组内容,改变原数组。
语法: array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
示例: let arr = [1, 2, 3]; arr.splice(0, 2, 'a', 'b'); // arr 变为 ['a', 'b', 3]
sort()
:对数组元素排序,并返回数组,改变原数组。
语法: array.sort([compareFunction])
示例: let arr = [3, 1, 4, 1]; arr.sort((a, b) => a - b); // arr 变为 [1, 1, 3, 4]
reverse()
:颠倒数组中元素顺序,并返回该数组,改变原数组。
语法: array.reverse()
示例: let arr = [1, 2, 3]; arr.reverse(); // arr 变为 [3, 2, 1]
map()
:创建一个新数组,该数组中每个元素是调用提供的函数后的返回值。
语法: array.map(function(currentValue, index, arr), thisValue)
示例: let arr = [1, 2, 3];let doubled = arr.map(x => x * 2); // doubled为[2, 4, 6]
every()
:测试一个数组内的所有元素是否都能通过被提供的函数测试。
语法: array.every(function(currentValue, index, arr), thisValue)
示例: let arr = [1, 2, 3]; let allPositive = arr.every(x => x > 0); // true
some()
:测试数组中是不是至少有1个元素通过了被提供的函数测试。
语法: array.some(function(currentValue, index, arr), thisValue)
示例: let arr = [1, -2, 3]; let hasPositive = arr.some(x => x > 0); // true
数组去重
- 使用
indexOf
/includes
循环判断实现
indexOf():
返回数组中可以找到一个给定元素的第一个索引,不存在则返回-1。
includes():
判断一个数组是否包含一个指定值,包含返回true,否则返回false。
let arr = [1, 2, 3, 4, 5, 5, 4];
const uniqueFn = arr => {
const res = []
arr.forEach(element => {
if(res.indexOf(element) === -1){
res.push(element)
}
});
return res
}
console.log(uniqueFn(arr));
let arr = [1, 2, 3, 4, 5,5,4];
const uniqueFn = arr => {
const res = []
arr.forEach(element => {
if(!res.includes(element)) {
res.push(element)
}
});
return res
}
console.log(uniqueFn(arr));
- 使用ES6的
Set实现
Set:
ES6引入的新数据结构,类似数组,但成员值唯一不重复。
let arr = [1, 2, 2, 3, 4, 4, 5];
let uniqueArr = [...new Set(arr)]; // 使用扩展运算符将Set转换回数组
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
let arr = [1, 2, 2, 3, 4, 4, 5];
let uniqueArr = Array.from(new Set(arr));
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
- 使用ES6的Map
实现
let arr = [1, 2, 3, 4, 5, 5, 4];
const uniqueFn = arr => {
let m = new Map();
arr.forEach(item => {
m.set(item, true);
});
console.log(m.keys());
return [...m.keys()];
}
console.log(uniqueFn(arr));
- 将数组值作为对象的键实现
基于数组的值创建一个索引时,对象的键唯一,实现去重。
let arr = [1, 2, 3, 4, 5, 5, 4];
const uniqueFn = arr => {
let obj = {};
arr.forEach(item => {
obj[item] = true; // 值可以是任意的,因为我们只关心键的唯一性
});
// 将对象的键转换回数组
return Object.keys(obj).map(Number); // 如果值是数字类型,可能需要转换回数字
}
console.log(uniqueFn(arr));
2.7 对象
- arguments:
类数组对象,没有数组方法,有length属性。包含传递给函数的所有参数。
可使用 Array.from()
转换为数组
function example() {
console.log(arguments.length); // 参数的数量 3
let argsArray = Array.from(arguments);
argsArray.push('new item');
console.log(argsArray); // 转换后的数组,可以添加新元素
}
example(1, 2, 3); // [1, 2, 3, 'new item']
- 获得对象上的属性:
for(let k in obj):以任意顺序迭代对象的可枚举属性名(包括继承的,除Symbol外)。
let obj = { a: 1, [Symbol('b')]: 2};
for (let k in obj) {
console.log(k, obj[k]); // 输出属性名和属性值 a 1
}
Object.keys(obj):返回数组,包括所有自身可枚举的属性名(除Symbol外)。
let obj = { a: 1, [Symbol('b')]: 2};
console.log(Object.keys(obj)); // ['a']
Object.getOwnPropertyNames(obj):返回数组,包含所有自身的属性名(含不可枚举属 性,除Symbol外)
let obj = { a: 1, [Symbol('b')]: 2 };
Object.defineProperty(obj, 'c', {
value: 3,
enumerable: false
});
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'c']
Object.getOwnPropertyDescriptors(obj):返回对象,该对象包含指定对象 obj
自身的所有属性的描述符。这些描述符是键值对,键是属性名,值是对应的属性描述符对象。属性描述符对象提供关于属性的各种信息,是否可枚举(enumerable)、是否可配置(configurable)、是否可写(writable)以及它的值(value)或访问器属性(getter/setter)等。
Reflect.ownKeys(obj):返回由目标对象自身的所有键组成的数组(含不可枚举属性,Symbol)
- JS监听对象属性的改变
Object.defineProperty
:允许添加或修改对象属性。当配置configurable: true
和enumerable: true
时,可监听属性变化。
let obj = {};
Object.defineProperty(obj, 'a', {
// 如已设置 set 或 get, 就不能设置 writable 和 value
// value: 1,
// writable: true,
enumerable: true,
configurable: true,
set: (newValue) =>{
console.log(`设置值 ${newValue}`);
this.a = newValue; // 内部属性
},
get: () =>{
console.log(`读取值`);
return this.a;
}
});
obj.a = 2; // 控制台输出:设置值 2
console.log(obj.a); // 控制台输出: 读取值 2
Proxy
:更强大的方式拦截和定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。Proxy
可用于实现复杂对象监听,比如数据绑定、依赖追踪等。
let handler = {
set: function(target, prop, value, receiver) {
console.log(`属性 ${prop} 被设置为 ${value}`);
target[prop] = value;
return true;
}
};
let p = new Proxy({}, handler);
p.a = 1; // 控制台输出:属性 a 被设置为 1
Object.defineProperty(user,'name',{
set:function(key,value){}
})
//缺点:如果id不在user对象中,则不能监听id的变化
var user = new Proxy({},{set:function(target,key,value,receiver){}})
//即使属性在user中不存在,通过user.id来定义也同样可以监听这个属性的变化
2.8 数据类型判断
- 基本数据类型
Number
、String
、Boolean
、Undefined
、Null
、Symbol
、BigInt:使用
typeof。
typeof null
返回 'object
'是特例
typeof null == 'object' // true,这是一个特例
typeof undefined == 'undefined' // true
typeof 42 == 'number' // true
typeof 'hello' == 'string' // true
typeof true == 'boolean' // true
typeof Symbol() == 'symbol' // true (ES6+)
typeof BigInt(123) == 'bigint' // true (ES2020+)
- 数组
Array.isArray()
:ES5引入,判断是否为数组。instanceof
:检查对象是否是Array的实例。constructor
:每个JS对象都有一个constructor
属性,指向创建该对象的构造函数。该方法可能因对象被修改或继承自其他构造函数而出现问题。Object.prototype.toString.call()
:可靠方法,返回表示对象内部[[Class]]
属性的字符串,对于数组,这个字符串是'[object Array]'
。
Array.isArray([]) // true
Array.isArray({}) // false
[] instanceof Array // true
({}) instanceof Array // false
[].constructor == Array // true
Object.prototype.toString.call([]) == '[object Array]' // true
- 任意类型
Object.prototype.toString.call():
可靠通用的方法,获取任何对象的内部[[Class]]
属性。
Object.prototype.toString.call([]) == '[object Array]' // true
Object.prototype.toString.call({}) == '[object Object]' // true
Object.prototype.toString.call(42) == '[object Number]' // true
2.9 ==、Object.is()
==:存在强制转换 number
===:类型不一致 直接返回false
"" == 0 //true
"0" == 0 //true
"" != "0" //true
123 == "123" //true
null == undefined //true ECMAScript规范
+0 == -0 //true
NaN==NaN // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
2.10 隐式转换
- 关系运算符 将其他数据类型转换成数字 Number()
- 逻辑非 将其他数据类型用Boolean()转换
console.log([] == 0); // true // Number([].valueOf().toString()) = 0
console.log(![] == 0); //true // !Boolean([]) == 0
console.log([] == ![]);// true // Number([].valueOf().toString()) = 0
console.log([] == []);// false
console.log({} == !{}); // false {}.valueOf().toString() = '[obejct object]''
console.log({} == {}); // false
3. JS作用域
变量声明
- var:声明变量是全局或整个函数块,具有函数作用域,存在变量提升。
- let、const:声明变量有块级作用域,无变量提升。let允许变量重新赋值,const不允许。
暂时性死区:尝试在声明前访问 let 、const声明的变量,引发 ReferenceError。
if (true) {
// 尝试在声明前访问 let 声明的变量
console.log(x); // ReferenceError: x is not defined
let x = 2;
}
function test() {
console.log(y); // ReferenceError: y is not defined
const y = 3;
}
test();
作用域
- 词法作用域/静态作用域:作用域在代码编写时就已经确定下来,而不是运行时决定。
- 块作用域:允许变量在代码块
{}
内部声明,且仅在该代码块内部可用。 - 函数作用域:变量在函数内声明,并在该函数内部及其内部声明的任何嵌套函数内可用。
- 全局作用域:最外层作用域声明的变量和函数挂载于window 对象,未定义直接赋值的变量。
var value = 1;
function foo(){
console.log(value); // 1
}
function bar(){
var value = 2;
foo();
}
bar();
// 全局作用域
for(var i=0;i<5;i++){
setTimeout(() => console.log(i),1000) // 一秒后输出5个5
}
for(var i = 0;i < lis.length; i++){
lis[i].addEventListener('click', e => alert(i), false) // click的时候i的值为lis.length
}
// let块级作用域
for(let i=0;i<5;i++){
setTimeout(() => console.log(i), 1000*i)
}
// 闭包 ES5立即执行函数
for(var i=0;i<5;i++){
(function(i){
setTimeout(()=>console.log(i),1000*i)
})(i)
}
相关笔试题
// 1.
(function() {
var a=b=3;
})()
console.log(a); // a is not defined
console.log(b);
// 2.
(function() {
var a=b=3;
})()
console.log(b); // 3
console.log(a); // a is not defined
// 考点:自执行 作用域 没使用var、let或const声明的变量(直接赋值)会成为全局变量(非严格模式下)
//var a; b=3; a=b;
// 3.
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i); // 4 4 4
}, 0)
}
// 考点:事件循环 宏任务 等待队列 异步 单线程 全局作用域
// 4.
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i); // 1 2 3
}, 0)
}
// 考点:事件循环 宏任务 等待队列 异步 单线程 块级作用域
// 5.
function fun(n) {
console.log(n); // 123
var n = 456;
console.log(n); // 456
}
var n = 123;
fun(n);
// 考点:预解析 作用域 参数
//fun(n){}, var n => n=123 => fun(n) => var n, n=123, log, n=456, log
// 6.
function fun() {
console.log(n); // undefined
var n = 456;
console.log(n); // 456
}
var n = 123;
fun(n);
// 考点:预解析 作用域 参数
//fun(){}, var n => n=123 => fun(n) => var n, log, n=456, log
// 7.
function fun() {
console.log(n); // 123
n = 456; // 修改全局
console.log(n); // 456
}
var n = 123;
fun(n);
// 考点:预解析 作用域 参数
//fun(){}, var n => n=123 => fun(n) => log, n=456, log
// 8.
function fun() {
console.log(n); // undefined
n = 456; // 修改全局
console.log(n); // 456
}
fun(n);
var n = 123;
// 考点:预解析 作用域 参数
//fun(){}, var n => fun(n) => log, n=456, log => n=456
// 9.
function fun() {
console.log(fun); // fun(){}
fun = 456; // 修改全局
console.log(fun); // 456
}
fun();
var fun = 123;
// 考点:预解析 作用域 参数 函数提升优先级 > 变量提升
//fun(){}, var fun => fun() => log, fun=456, log => fun=123
// 10.
function fun() {
console.log(fun);
fun = 456;
console.log(fun);
}
var fun = 123;
fun(); // fun is not a function
// 考点:预解析 作用域 参数 函数提升优先级 > 变量提升
//fun(){}, var fun => fun=123 => fun()
// 11.
var fun = 123;
function fun() {
console.log(fun);
fun = 456; //
console.log(fun);
}
fun(); // fun is not a function
// 考点:预解析 作用域 参数 函数提升优先级 > 变量提升
// fun(){} => fun=123 => fun()
// 12.
function b() {
console.log(a); // a(){}
var a = 10;
function a(){}
a = 100;
console.log(a); // 100
}
b();
// 考点:预解析 函数提升优先级 > 变量提升
//b(){} => b() => a(){} var a => log, a = 10, a = 100
// 13.
(function b(num) {
console.log(num); // num(){}
var num = 10;
function num(){}
})(100)
// 考点:预解析 自执行函数 函数提升优先级 > 参数 > 变量提升
//b(){} => num() => var num
// 14.
function m() {
console.log(a1); // undefined
console.log(a2); // undefined
console.log(b1); // undefined
console.log(b2); // undefined
if(false) {
function b1() {}
var a1 = 100
}
if(true) {
function b2() {}
var a2 = 10
}
console.log(a1); // undefined
console.log(a2); // 10
console.log(b1); // undefined
console.log(b2); // b2() {}
}
m()
// 考点:代码未执行到代码块无预解析
//log log log log => b2, var a2 => a2 =10
// 15.
var n = 123
function f1() {
console.log(n); // 123
}
function f2() {
var n = 456
f1(); // 无调用者,作用域window
}
f2() // 无调用者,作用域window
console.log(n); // 123
// 考点:预解析 作用域
// 16.
var n = 123
function f1() {
console.log(n); // 123
}
function f2() {
var n = 456
f1(n); // 无调用者,作用域window
}
f2() // 无调用者,作用域window
console.log(n); // 123
// 考点:预解析 作用域 沿着所在作用域链向上查找
// 17.
var n = 123
function f1(n) {
console.log(n); // 456
}
function f2() {
var n = 456
f1(n); // 无调用者,作用域window
}
f2() // 无调用者,作用域window
console.log(n); // 123
// 考点:预解析 作用域 沿着所在作用域链向上查找
// 18.
function f(s) {
console.log(this.a, s); // 2 arguments this = obj
return this.a + s // 2[object Arguments]
}
var obj = {
a: 2
}
var f2 = function(){
return f.call(obj, arguments)
}
var b = f2(3); // f.call(obj, arguments)
console.log(b); // 2[object Arguments]
// 考点:预解析 作用域 字面量 call argumens
// 19.
function f(s) {
console.log(this.a, s); // 2 3 this = obj
return this.a + s // 5
}
var obj = {
a: 2
}
var f2 = function(){
return f.apply(obj, arguments)
}
var b = f2(3); // f.apply(obj, arguments)
console.log(b); // 5
// 考点:预解析 作用域 字面量 apply argumens
// 20.
var a=11;
function test2(){
this.a=22;
let b = () => {console.log(this.a)}
b();
}
var x = new test2(); // 22
4. 闭包
闭包:有权访问其他函数作用域局部变量的函数
作用:私有作用域保存变量,避免全局变量污染,扩展了局部变量生命周期和作用范围,但常驻内存会引起内存泄漏
应用
- 防抖:多次触发事件,事件处理函数在n秒内只执行一次,且在触发操作结束时执行。若n秒内又被触发,则重新计时。场景:输入框验证、窗口大小调整、下拉触底加载、即时查询、scroll事件。
function debounce(fn, wait) {
let timer = null;
return function (...args) {
const context = this
if(!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null
}, wait);
}
}
}
// 等待wait后执行事件 过滤重复的验证事件(用户输入停止后300ms触发验证)
function debounce(fn, wait = 300) {
let timer = null;
return function(...args) {
const context = this;
if(timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(context, args), wait);
};
}
// scroll事件
let timer;
window.onscroll = function () {
if(timer) clearTimeout(timer)
timer = setTimeout(() => {
//滚动条位置
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
timer = null;
},200)
}
// 立即执行事件,等待wait后再次执行事件
function debounceImmediate(fn, wait) {
let timer;
let isImmediate = true; // 标志位,用于判断是否应该立即执行
return function(...args) {
const context = this;
if (isImmediate) {
fn.apply(context, args);
isImmediate = false;
}
if(timer) clearTimeout(timer);
// 设置新的定时器,在wait时间后执行
timer= setTimeout(() => {
fn.apply(context, args);
// 可以在这里重新设置isImmediate为true,如果你想让下一次调用再次立即执行
// 但通常防抖函数不需要这样做,除非你有特别的场景需求
}, wait);
};
}
- 节流:一定时间只调用一次 ,连续发生事件在n秒内只执行一次,场景:提交表单,滚动监听
function throttleSimple(fn, wait = 300) {
let lastRan;
return function(...args ) {
const context = this, now = Date.now();
if (!lastRan || (now - lastRan >= wait)) {
fn.apply(context, args);
lastRan = now;
}
};
}
- 计算结果缓存:利用闭包将计算结果存储在函数内部变量中,避免重复计算。
function createCalculator(base) {
let result = null; // 用于缓存计算结果
return function(x) {
if (result === null) {
// 如果没有缓存结果,则进行计算并缓存
result = base + x;
}
return result; // 返回缓存的结果
};
}
const addFive = createCalculator(5);
console.log(addFive(10)); // 输出: 15
console.log(addFive(20)); // 输出: 15,因为结果已经被缓存
- 模仿块级作用域:使用匿名自执行函数模拟块级作用域,避免污染全局作用域。
(function() {
let localVariable = "I am local";
console.log(localVariable); // 可以访问
})();
console.log(localVariable); // 报错:localVariable is not defined
- 保存外部函数this:外部函数声明
let that = this,setTimeout
回调函数可访问外部this。
function outerFunction() {
let that = this; // 保存外部函数的this
setTimeout(function() {
console.log(that === obj ); // true,因为that指向了outerFunction
}, 1000);
}
const obj = { name: "Outer Object" }
outerFunction.call(obj); // 使用call改变outerFunction的this指向
- 封装私有变量:单例模式,库的封装、类和继承,vue中data(){} 保证每个组件的私有作用域
// 模拟单例模式
const Singleton=(function(){
const instance;
const CreateSingleton = function(name){
this.name = name;
if(instance) return instance;
this.getName(); // 打印实例名字
return instance = this;
}
CreateSingleton.prototype.getName = function(){
console.log(this.name) // 获取实例名字
}
return CreateSingleton;
})();
//创建实例对象1
const a = new Singleton('a');
//创建实例对象2
const b = new Singleton('b');
console.log(a===b);
5. 类的创建和继承
5.1 构造函数的new
// 三者区别
let obj = Object.create(null);
let obj2 = {}; // 等同于 let obj2 = new Object();
console.log(obj); // {} 无属性 不存在__proto__
console.log(obj2); // {} 存在__proto__
console.log(obj2.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
// 1、创建一个空对象
let obj = new Object();
// 2、设置空对象的对象原型__proto__指向构造函数的原型对象prototype
obj.__proto__ = Person.prototype;
// 3、执行构造函数,为新对象添加属性和方法,将this指向新对象
let result = Person.call(obj);
// 4、判断返回值类型 返回新对象
person = typeof(result) == "object" ? result : obj;
5.2 构造函数的return
- 构造函数的函数体执行完毕时,不使用return,构造函数调用表达式的结果就是新对象的值
- 使用return但没有指定返回值,或返回一个原始值,将忽略返回值使用新对象作为调用结果
- 使用return语句返回一个对象,调用表达式的值就是这个对象
5.3 类的创建(es5):new一个function,在这个function的prototype里面增加属性和方法
//定义一个动物类
function Animal(name){
//实例属性
this.name = name||'Animal';
//实例方法
this.sleep = function(){
console.log(this.name+'正在睡觉!');
}
}
//原型方法
Animal.prototype.eat = function(food){
console.log(this.name+'正在吃:'+food);
}
// Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
5.4 类的继承:
原型链继承:
- 核心:子构造函数的
prototype
指向父构造函数的实例。 - 特点:
- 继承父类原型的属性和方法。
- 子类的实例同时是父类的实例。
- 无法实现多继承。
- 无法向父类构造函数传参。
- 原型中所有属性被所有实例共享。
- 新增属性和方法不应放在构造器中,应放在原型上。
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name='cat';
var cat = new Cat();
console.log(cat.name);// cat
console.log(cat.eat('fish'));// cat正在吃:fish
console.log(cat.sleep()); // cat正在睡觉!
console.log(cat instanceof Animal);//true
console.log(cat instanceof Cat);//true
构造继承:
- 核心:在子类型构造函数中调用父类型构造函数。
- 特点:
- 解决子类实例共享父类引用属性的问题。
- 可以向父类传参。
- 可通过
call
或apply
实现多继承(模拟)。 - 实例仅是子类的实例,只能继承父类的实例属性和方法,不能继承原型上的属性和方法。
- 每个子类实例都包含父类的实例方法,可能影响性能。
function Cat(name){
Animal.call(this);
this.name = name ||'Tom';
}
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal);//false
console.log(cat instanceof Cat);//true
组合继承:
- 核心:结合构造继承和原型链继承。
- 调用父类构造函数继承实例属性和方法,允许传参。
- 通过将父类实例赋值给子类原型来继承原型属性和方法。
- 特点:
- 子类实例既是子类的也是父类的实例。
- 不存在引用属性共享问题。
- 调用了两次父类构造函数(通常视为缺点,因为生成了两份实例)。
- 综合了构造继承和原型链继承的优点。
function Cat(name){
Animal.call(this);
this.name = name||'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat);//true
寄生组合继承:不会初始化两次实例方法/属性
function Cat(name){
Animal.call(this);
this.name = name ||'Tom';
}
(function(){ //创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;//将实例作为子类的原型
Cat.prototype = new Super();
})();
var cat=new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal);//true
console.log(cat instanceof Cat);//true
其他继承方式简述:
- 实例继承:不常用,为父类实例添加新特性,作为子类实例返回。实例不是子类实例,不支持多继承。
- 拷贝继承:拷贝父类的属性和方法,支持多继承,但效率低、内存占用高,无法获取父类不可枚举方法。
ES6中的继承
-
class关键字:ES6引入了class关键字,使得继承更加直观和易于理解。
-
extends关键字:使用extends关键字来指定一个类的父类。
-
super关键字:在子类中调用父类的方法或属性时,需要使用super关键字。
5.5 原型链
- 构造函数默认带有属性prototype原型对象,prototype具有constructor属性,指向构造函数
- 每个实例对象有一个proto 属性指向prototype,当读取实例属性时,如果实例中找不到,就查找实例prototype原型中的属性,如果还查不到,就找原型的原型,沿原型链一直找到最顶层
- 原型链顶端是Object.prototype,通过Object构造函数生成,Object.prototype没有原型,Object.prototype.proto===null
- proto:绝大部分浏览器支持这个非标准的方法访问原型,实际上它来自Object.prototype
Object.getPrototypeOf()
方法返回指定对象的原型(内部[[Prototype]]
属性的值)
console.log(Object.getPrototypeOf([]).constructor); // Array
- Function.proto == Object.prototype // false;Function.proto == Function.prototype // true
console.log(Function.__proto__ == Object.prototype); // false
console.log(Function.__proto__ == Function.prototype); // true
Function.prototype.a = 1;
Object.prototype.b = 2;
function A(){}
var a = new A();
console.log(a.a,a.b); // undefined, 2
console.log(A.a,A.b); // 1,2
5.6 es6类修饰符
- public:修饰属性或方法是公有的,可在任何地方被访问,默认所有属性和方法是 public
- protected:修饰属性或方法是受保护的,仅在类中、子类中允许被访问
- private:修饰属性或方法是私有的,仅在类中允许被访问