【前端面试】四、JavaScript 1-5

目录

1. JS语言特性

2. JS数据类型

3. JS作用域

4. 闭包

5. 类的创建和继承


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.jsdecimal.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时

空值合并运算符 '??'

  • || 返回第一个 值,无法区分 false0、空字符串 ""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: trueenumerable: 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 数据类型判断

  • 基本数据类型

  NumberStringBooleanUndefinedNullSymbolBigInt:使用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+)
  • 数组
  1. Array.isArray():ES5引入,判断是否为数组。
  2. instanceof:检查对象是否是Array的实例。
  3. constructor:每个JS对象都有一个constructor属性,指向创建该对象的构造函数。该方法可能因对象被修改或继承自其他构造函数而出现问题。
  4. 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

构造继承

  • 核心:在子类型构造函数中调用父类型构造函数。
  • 特点
    • 解决子类实例共享父类引用属性的问题。
    • 可以向父类传参。
    • 可通过callapply实现多继承(模拟)。
    • 实例仅是子类的实例,只能继承父类的实例属性和方法,不能继承原型上的属性和方法。
    • 每个子类实例都包含父类的实例方法,可能影响性能。
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关键字。

参看文献:JS实现继承的几种方式 - 幻天芒 - 博客园

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:修饰属性或方法是私有的,仅在类中允许被访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值