一天10个JS面试题(三)

目录:
1、如何在 JavaScript 中比较两个对象?
2、JavaScript 中的作用域、预解析与变量声明提升?
3、什么是作用域链?
4、变量提升与函数提升的区别?
5、数组去重有哪些办法?
6、防抖和节流怎么实现的?
7、说一下深拷贝和浅拷贝?
8、闭包是什么?怎么实现?
9、作用域是什么?
10、src 和 href 的区别是?

1、如何在 JavaScript 中比较两个对象?

- **浅比较**适用于比较简单的对象。
- **深度比较**适用于比较嵌套对象,可以递归地比较每一层属性。
- **JSON 序列化**方法简单但有限制。
- **Lodash**提供了现成的解决方案,适用于需要高效和可靠的深度比较。
- **手动递归比较**则可以处理更多特殊情况和类型。

浅比较
浅比较只比较对象的第一层属性,适用于简单的对象结构。

function shallowEqual(obj1, obj2) {
    if (obj1 === obj2) {
        return true;
    }

    if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
        return false;
    }

    let keys1 = Object.keys(obj1);
    let keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (let key of keys1) {
        if (obj1[key] !== obj2[key]) {
            return false;
        }
    }

    return true;
}

深度比较
深度比较会递归地比较对象的每一层属性。

function deepEqual(obj1, obj2) {
    if (obj1 === obj2) {
        return true;
    }

    if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
        return false;
    }

    let keys1 = Object.keys(obj1);
    let keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (let key of keys1) {
        if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

使用 JSON 序列化
这是一个简单但不总是可靠的方法,因为它不能处理函数、`undefined`、循环引用等。

function jsonEqual(obj1, obj2) {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
}

使用 Lodash 库
Lodash 提供了一个强大的深度比较函数 `_.isEqual`,可以用来比较两个对象。

// 首先需要安装 Lodash 库
// npm install lodash

const _ = require('lodash');

let obj1 = { a: 1, b: { c: 2 } };
let obj2 = { a: 1, b: { c: 2 } };

console.log(_.isEqual(obj1, obj2)); // true

手动递归比较
如果你想要更灵活地处理各种数据类型和特殊情况,可以手动编写一个递归比较函数。

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}

function deepCompare(obj1, obj2) {
    if (obj1 === obj2) {
        return true;
    }

    if (!isObject(obj1) || !isObject(obj2)) {
        return false;
    }

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (const key of keys1) {
        if (!keys2.includes(key) || !deepCompare(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

2、JavaScript 中的作用域、预解析与变量声明提升?

1、作用域:决定了变量的可访问性。全局作用域和局部作用域是最基本的两种作用域类型。
2、预解析:(Hoisting)是指 JavaScript 引擎在执行代码前会将变量和函数声明提升到其作用域的顶部。
3、变量声明提升:`var` 声明的变量会提升但不会初始化,`let` 和 `const` 声明的变量会提升但在声明前访问会抛出 `ReferenceError`。
4、函数声明提升:整个函数声明会被提升,而函数表达式仅提升变量部分。

作用域(Scope)
作用域决定了变量和函数的可访问性。全局作用域和局部作用域。

全局作用域:在代码的任何地方都可以访问的变量。

  var globalVar = "I am a global variable";
  
  function foo() {
    console.log(globalVar); // 可以访问 globalVar
  }
  
  foo();

局部作用域:在函数内部声明的变量,只能在函数内部访问。

  function bar() {
    var localVar = "I am a local variable";
    console.log(localVar); // 可以访问 localVar
  }
  
  bar();
  console.log(localVar); // Uncaught ReferenceError: localVar is not defined

预解析(Hoisting)
JavaScript 引擎在执行代码之前,会先进行解析阶段。在这个阶段,变量和函数声明会被提升到其作用域的顶部。这就是所谓的“变量声明提升”(hoisting)。

变量提升
使用 `var` 声明的变量会被提升,但不会初始化。只有声明被提升,而赋值不会。

console.log(foo); // 输出: undefined
var foo = "Hello, world!";
console.log(foo); // 输出: Hello, world!

上述代码在解析时会被理解为:

var foo;
console.log(foo); // 输出: undefined
foo = "Hello, world!";
console.log(foo); // 输出: Hello, world!

使用 `let` 和 `const` 声明的变量也会提升,但不会初始化,在声明之前访问会导致 ReferenceError。

console.log(bar); // Uncaught ReferenceError: Cannot access 'bar' before initialization
let bar = "Hello, let!";

console.log(baz); // Uncaught ReferenceError: Cannot access 'baz' before initialization
const baz = "Hello, const!";

函数提升
函数声明会被完整地提升,包括函数体

console.log(myFunction()); // 输出: Hello from myFunction!

function myFunction() {
    return "Hello from myFunction!";
}

上述代码在解析时会被理解为

function myFunction() {
    return "Hello from myFunction!";
}

console.log(myFunction()); // 输出: Hello from myFunction!

函数表达式则不会提升,仅会提升变量声明部分。

console.log(myFunc); // 输出: undefined
console.log(myFunc()); // Uncaught TypeError: myFunc is not a function

var myFunc = function() {
    return "Hello from myFunc!";
};

上述代码在解析时会被理解为:

var myFunc;

console.log(myFunc); // 输出: undefined
console.log(myFunc()); // Uncaught TypeError: myFunc is not a function

myFunc = function() {
    return "Hello from myFunc!";
};

3. 变量声明提升(Hoisting)
var声明:变量声明提升到作用域顶部,但初始化留在原地,未赋值时默认值为 `undefined`。
let和const声明:变量也会提升,但不会初始化,且在声明之前访问会抛出 `ReferenceError`。
函数声明:整个函数声明(包括函数体)会提升。
函数表达式:仅提升变量声明部分,不提升赋值部分。

作用域与提升的综合示例

function testHoisting() {
    console.log(a); // 输出: undefined
    console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
    console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
    
    var a = 'var variable';
    let b = 'let variable';
    const c = 'const variable';
    
    console.log(a); // 输出: var variable
    console.log(b); // 输出: let variable
    console.log(c); // 输出: const variable
}

testHoisting();

上述代码在解析时会被理解为

function testHoisting() {
    var a;
    let b;
    const c;

    console.log(a); // 输出: undefined
    console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
    console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
    
    a = 'var variable';
    b = 'let variable';
    c = 'const variable';
    
    console.log(a); // 输出: var variable
    console.log(b); // 输出: let variable
    console.log(c); // 输出: const variable
}

testHoisting();

3、什么是作用域链?

作用域链:由多个作用域对象按嵌套顺序组成,用于变量解析。
变量查找:沿着作用域链从当前作用域逐层向上查找,直到全局作用域。
函数嵌套:每个嵌套函数会在其作用域链中包含所有上级作用域。
闭包:函数可以记住并访问它被创建时的作用域链。

1. 作用域链的定义

在 JavaScript 中,每当函数被创建时,都会创建一个执行上下文(execution context)。每个执行上下文都有一个作用域链(scope chain),用于解析变量。当代码在函数内部执行时,它需要查找变量的值,这时会根据作用域链进行查找。

2. 作用域链的结构

作用域链是由多个作用域对象按嵌套顺序组成的列表。这个列表从当前执行上下文的作用域开始,逐级向上,直到全局作用域(global scope)。

3. 作用域链的查找机制

当 JavaScript 需要查找一个变量时,会沿着作用域链从当前作用域开始逐层向上查找,直到找到所需的变量或到达全局作用域。如果在全局作用域中也没有找到该变量,则会抛出 `ReferenceError`。

4. 作用域链的例子

下面是一个具体的例子来说明作用域链的工作机制

var globalVar = "I am global";

function outerFunction() {
    var outerVar = "I am outer";

    function innerFunction() {
        var innerVar = "I am inner";
        console.log(innerVar);    // "I am inner"
        console.log(outerVar);    // "I am outer"
        console.log(globalVar);   // "I am global"
    }

    innerFunction();
}

outerFunction();

在这个例子中:
- 当 `innerFunction` 执行时,它的作用域链包含以下作用域:
  1. `innerFunction` 的局部作用域
  2. `outerFunction` 的局部作用域
  3. 全局作用域

当 `innerFunction` 内的 `console.log` 语句需要访问变量时,会按以下顺序查找:
- `innerVar` 在 `innerFunction` 的局部作用域中找到。
- `outerVar` 在 `innerFunction` 的局部作用域中找不到,沿着作用域链向上在 `outerFunction` 的局部作用域中找到。
- `globalVar` 在 `innerFunction` 和 `outerFunction` 的局部作用域中都找不到,继续沿着作用域链在全局作用域中找到。

5. 函数嵌套和作用域链

嵌套函数的例子可以更好地说明作用域链的概念:

var a = 1;

function firstFunction() {
    var b = 2;

    function secondFunction() {
        var c = 3;

        function thirdFunction() {
            var d = 4;
            console.log(a);  // 1
            console.log(b);  // 2
            console.log(c);  // 3
            console.log(d);  // 4
        }

        thirdFunction();
    }

    secondFunction();
}

firstFunction();

在这个例子中:
- 当 `thirdFunction` 执行时,它的作用域链包含以下作用域:
  1. `thirdFunction` 的局部作用域
  2. `secondFunction` 的局部作用域
  3. `firstFunction` 的局部作用域
  4. 全局作用域

变量的查找顺序和之前解释的一样,从当前作用域开始逐层向上查找。

6. 闭包和作用域链

闭包是指函数可以记住并访问它被创建时的作用域链,即使这个函数在其作用域之外执行。

function createCounter() {
    let count = 0;
    return function() {
        count += 1;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

在这个例子中:
- `createCounter` 创建了一个闭包,其中 `count` 变量的值在多次调用 `counter` 时得以保留和更新。
- 每次调用 `counter`,它都能访问 `createCounter` 的作用域中的 `count` 变量,因为 `counter` 记住了它的作用域链。


4、变量提升与函数提升的区别?

1. 变量提升(Variable Hoisting)

变量提升是指在 JavaScript 中,变量声明会被提升到其作用域的顶部。这意味着,无论变量声明出现在代码的什么位置,在代码执行时,它们都会被认为是在作用域的顶部声明的。然而,使用 `var` 关键字声明的变量在提升时仅提升声明部分,不会提升赋值部分,因此在赋值之前变量的值是 `undefined`。

 变量提升的例子

console.log(foo); // 输出: undefined
var foo = "Hello, world!";
console.log(foo); // 输出: "Hello, world!"

上述代码在解析时会被理解为:

var foo;
console.log(foo); // 输出: undefined
foo = "Hello, world!";
console.log(foo); // 输出: "Hello, world!"

使用 `let` 和 `const` 声明的变量也会提升,但不会在声明之前初始化。在提升之前访问它们会抛出 `ReferenceError`

console.log(bar); // Uncaught ReferenceError: Cannot access 'bar' before initialization
let bar = "Hello, let!";

console.log(baz); // Uncaught ReferenceError: Cannot access 'baz' before initialization
const baz = "Hello, const!";

2. 函数提升(Function Hoisting)

函数提升是指在 JavaScript 中,函数声明会被完整地提升到其作用域的顶部。这意味着,无论函数声明出现在代码的什么位置,在代码执行时,它们都会被认为是在作用域的顶部声明的。与变量提升不同,函数提升不仅提升声明部分,还提升函数体。

函数提升的例子

console.log(myFunction()); // 输出: "Hello from myFunction!"

function myFunction() {
    return "Hello from myFunction!";
}

上述代码在解析时会被理解为

function myFunction() {
    return "Hello from myFunction!";
}

console.log(myFunction()); // 输出: "Hello from myFunction!"

需要注意的是,函数表达式不会提升,只有声明部分会被提升,赋值部分不会提升。

console.log(myFunc); // 输出: undefined
console.log(myFunc()); // Uncaught TypeError: myFunc is not a function

var myFunc = function() {
    return "Hello from myFunc!";
};

上述代码在解析时会被理解为:

var myFunc;

console.log(myFunc); // 输出: undefined
console.log(myFunc()); // Uncaught TypeError: myFunc is not a function

myFunc = function() {
    return "Hello from myFunc!";
};

3. 区别总结

- **变量提升**:使用 `var` 声明的变量提升只提升声明,不提升赋值。使用 `let` 和 `const` 声明的变量也会提升,但在初始化之前访问会抛出 `ReferenceError`。
- **函数提升**:函数声明会完整地提升,包括函数体。函数表达式不会提升,只有声明部分提升,赋值部分不提升。### 4. 变量提升与函数提升的综合例子

console.log(a); // 输出: undefined
console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
console.log(foo()); // 输出: "Hello from foo!"
console.log(bar); // 输出: undefined
console.log(bar()); // Uncaught TypeError: bar is not a function

var a = 1;
let b = 2;
const c = 3;

function foo() {
    return "Hello from foo!";
}

var bar = function() {
    return "Hello from bar!";
};

上述代码在解析时会被理解为:

var a;
let b;
const c;
function foo() {
    return "Hello from foo!";
}
var bar;

console.log(a); // 输出: undefined
console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
console.log(c); // Uncaught ReferenceError: Cannot access 'c' before initialization
console.log(foo()); // 输出: "Hello from foo!"
console.log(bar); // 输出: undefined
console.log(bar()); // Uncaught TypeError: bar is not a function

a = 1;
b = 2;
c = 3;

bar = function() {
    return "Hello from bar!";
};

5、数组去重有哪些办法?

1、使用 `Set` 是最简洁和高效的方法,适用于大多数情况。
2、使用 `filter` 和 `indexOf` 或 `reduce` 和 `includes` 适用于希望通过内置数组方法去重的场景。
3、使用 `forEach` 和 `includes` 或 `Map` 提供了更多自定义操作的可能。
4、Lodash 库提供了简洁的 API,适用于大型项目中频繁的数组操作。
5、对于数值数组,排序和一遍遍历的方法在某些特定场景下可能更高效。

 1. 使用 `Set`
`Set` 是一种集合数据结构,能自动去重。

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

2. 使用 `filter` 和 `indexOf`
通过 `filter` 方法结合 `indexOf` 方法来去重。

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

3. 使用 `reduce` 和 `includes`
通过 `reduce` 方法结合 `includes` 方法来去重。

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = array.reduce((acc, item) => {
    if (!acc.includes(item)) {
        acc.push(item);
    }
    return acc;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

4. 使用 `forEach` 和 `includes`
通过 `forEach` 方法结合 `includes` 方法来去重。

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = [];
array.forEach(item => {
    if (!uniqueArray.includes(item)) {
        uniqueArray.push(item);
    }
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

5. 使用 `forEach` 和 `Map`
通过 `forEach` 方法结合 `Map` 数据结构来去重。

let array = [1, 2, 2, 3, 4, 4, 5];
let map = new Map();
array.forEach(item => {
    if (!map.has(item)) {
        map.set(item, true);
    }
});
let uniqueArray = Array.from(map.keys());
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

6. 使用 Lodash 库的 `uniq` 方法
使用 Lodash 库可以简化许多常见操作,去重也不例外

// 首先需要安装 Lodash 库
// npm install lodash

const _ = require('lodash');

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = _.uniq(array);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

7. 使用 ES6 的 `Set` 和 `Array.from`
另一种使用 `Set` 去重的方法。

let array = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = Array.from(new Set(array));
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

8. 使用排序和一遍遍历(适用于数值数组)
先排序数组,然后一遍遍历去重。

let array = [1, 2, 2, 3, 4, 4, 5];
array.sort((a, b) => a - b);

let uniqueArray = array.filter((item, index) => {
    return index === 0 || item !== array[index - 1];
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

6、防抖和节流怎么实现的?

防抖(Debouncing)和节流(Throttling)是优化高频率事件处理的重要技术。它们都旨在控制函数执行的频率,但适用于不同的场景。以下是防抖和节流的实现及其适用场景。

1. 防抖(Debouncing)

防抖是指在事件被触发后一定时间内不再触发才执行函数。如果在这段时间内再次触发事件,则重新计时。适用于避免在短时间内多次触发同一事件,例如搜索框输入、窗口调整大小等。

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(this, args);
        }, wait);
    };
}
const debouncedFunction = debounce(() => {
    console.log('Function executed!');
}, 300);

window.addEventListener('resize', debouncedFunction);

2. 节流(Throttling)

节流是指在连续事件触发时,保证一定时间段内只调用一次事件处理函数。适用于控制高频事件触发的执行频率,例如滚动事件、窗口缩放事件等。

function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}
const throttledFunction = throttle(() => {
    console.log('Function executed!');
}, 1000);

window.addEventListener('scroll', throttledFunction);

3. 防抖和节流的区别及适用场景

- **防抖(Debouncing)**:
  - **适用场景**:输入框输入、窗口大小调整、按钮点击(短时间内避免多次点击)。
  - **特点**:事件停止触发后一定时间才执行,避免短时间内频繁调用。
  - **实现逻辑**:每次触发事件都重新计时,只有在指定时间内没有再次触发事件时才执行。

- **节流(Throttling)**:
  - **适用场景**:滚动事件、窗口滚动、鼠标移动(需要在一段时间内只执行一次)。
  - **特点**:在连续触发事件时,以固定的时间间隔执行函数。
  - **实现逻辑**:保证在一定时间内只执行一次函数,不管事件触发了多少次。

防抖和节流都是控制函数执行频率的有效手段,选择哪一种取决于具体应用场景。防抖适用于用户停止操作后再执行函数的情况,而节流适用于在连续操作中定期执行函数的情况。理解并合理使用这两种技术可以显著提高应用的性能和用户体验。


7、说一下深拷贝和浅拷贝?

浅拷贝(Shallow Copy)

浅拷贝是指复制对象时只复制一层对象属性,对于嵌套的子对象,浅拷贝只复制其引用。这意味着如果嵌套对象的内容发生变化,浅拷贝的对象也会受到影响。

1. **使用 `Object.assign`

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

obj2.b.c = 3;
console.log(obj1.b.c); // 输出: 3

2. **使用展开运算符

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

obj2.b.c = 3;
console.log(obj1.b.c); // 输出: 3

深拷贝(Deep Copy)

深拷贝是指复制对象时,完全复制对象及其嵌套的子对象。这样,拷贝得到的对象与原对象完全独立,对其中一个对象的修改不会影响另一个对象。
1. **使用 JSON 序列化**

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

obj2.b.c = 3;
console.log(obj1.b.c); // 输出: 2

这种方法的局限性:
- 无法处理函数和 `undefined`。
- 无法处理循环引用。

2. **使用递归**

实现一个递归函数来处理深拷贝:

function deepCopy(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    if (Array.isArray(obj)) {
        const arrCopy = [];
        obj.forEach((item, index) => {
            arrCopy[index] = deepCopy(item);
        });
        return arrCopy;
    }

    const objCopy = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            objCopy[key] = deepCopy(obj[key]);
        }
    }
    return objCopy;
}

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = deepCopy(obj1);
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

obj2.b.c = 3;
console.log(obj1.b.c); // 输出: 2

3. **使用第三方库**

可以使用一些第三方库,如 Lodash 的 `cloneDeep` 方法。

// 首先需要安装 Lodash 库
// npm install lodash

const _ = require('lodash');

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = _.cloneDeep(obj1);
console.log(obj2); // 输出: { a: 1, b: { c: 2 } }

obj2.b.c = 3;
console.log(obj1.b.c); // 输出: 2

总结

- **浅拷贝**:
  - 只复制对象的第一层属性。
  - 嵌套对象复制的是引用,改变嵌套对象会影响原对象。
  - 常用方法:`Object.assign`、展开运算符(`...`)。

- **深拷贝**:
  - 递归地复制对象的所有层次属性。
  - 嵌套对象完全独立,改变拷贝后的对象不会影响原对象。
  - 常用方法:JSON 序列化(`JSON.parse(JSON.stringify(obj))`)、递归函数、Lodash 的 `cloneDeep`。


8、闭包是什么?怎么实现?

闭包是 JavaScript 中一个强大的特性,它允许函数记住并访问其词法作用域中的变量。通过理解闭包的工作原理和应用场景,开发者可以编写更有效和灵活的代码。以下是闭包的关键点:

- 闭包是函数与其词法环境的组合。
- 闭包允许函数在其词法作用域之外执行时仍然能够访问其作用域中的变量。
- 闭包常用于创建私有变量、函数工厂、回调函数和缓存。

闭包(Closure)是 JavaScript 中一个重要的概念,它使得函数可以记住并访问它的词法作用域,即使函数是在其词法作用域之外执行的。闭包是函数与其词法环境的组合,它允许函数访问其外部作用域的变量。

闭包的定义和特性

闭包是指有权访问另一个函数作用域中的变量的函数。闭包使得内部函数可以访问外部函数的变量,即使在外部函数已经执行完毕之后。

闭包的特性
1. **函数嵌套**:闭包是在一个函数内部定义的另一个函数。
2. **变量访问**:闭包可以访问其定义时所在的词法作用域中的变量。
3. **持久化**:闭包中的变量可以持久化,因为闭包可以记住它创建时的词法环境。

闭包的实现

实现闭包的关键是函数的嵌套和变量作用域的访问。以下是几个使用闭包的例子:

1. 简单的闭包示例

function outerFunction() {
    let outerVariable = 'I am outside!';
    
    function innerFunction() {
        console.log(outerVariable); // 可以访问 outerFunction 的变量
    }
    
    return innerFunction;
}

const myClosure = outerFunction();
myClosure(); // 输出: "I am outside!"

在这个例子中,`innerFunction` 是一个闭包,它可以访问 `outerFunction` 中的变量 `outerVariable`,即使 `outerFunction` 已经执行完毕。

2. 使用闭包实现私有变量闭包常用于创建私有变量,这些变量不能从外部直接访问。

function createCounter() {
    let count = 0; // 私有变量
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.decrement()); // 输出: 1
console.log(counter.getCount()); // 输出: 1

在这个例子中,`count` 变量是私有的,只能通过 `increment`、`decrement` 和 `getCount` 方法访问和修改。

3. 延迟执行和闭包

闭包可以用于创建延迟执行的函数,并保持对变量的访问。

function delayedMessage(message, delay) {
    setTimeout(function() {
        console.log(message);
    }, delay);
}

delayedMessage('Hello after 1 second', 1000); // 1 秒后输出: "Hello after 1 second"

在这个例子中,匿名函数是一个闭包,它保持了对 `message` 变量的引用,尽管 `delayedMessage` 已经执行完毕。

闭包的应用场景

1. **数据封装和私有变量**:通过闭包,可以创建私有变量和方法,防止外部访问。
2. **函数工厂**:闭包可以用于创建工厂函数,生成带有特定环境的函数。
3. **回调函数和事件处理**:在回调函数和事件处理程序中,闭包很常用,因为它们需要访问创建它们时的环境。
4. **缓存**:闭包可以用于缓存计算结果,以提高性能。


9、作用域是什么?

变量只在当前函数或者对象内有效

作用域(Scope)指的是在程序中定义变量、函数和对象的区域或上下文。作用域决定了这些变量、函数和对象在代码的哪些部分是可见的或可访问的

1. 全局作用域(Global Scope)
   定义在全局范围内的变量或函数可以在程序的任何地方访问。
   在JavaScript中,全局作用域中的变量是在顶层定义的,通常是脚本文件的外部或者函数之外。

2. 局部作用域(Local Scope)
   局部作用域的变量或函数只能在它们定义的特定部分或块中访问。
   例如,在函数内定义的变量只在该函数内可访问。

3. 块级作用域(Block Scope)
   块级作用域是由花括号 `{}` 创建的,包含在花括号内的变量和常量只在这个块内有效。
   ES6引入的`let`和`const`关键字在JavaScript中创建了块级作用域。
   在其他一些编程语言中,例如C++和Java,块级作用域是默认的。

4. 词法作用域(Lexical Scope)
   词法作用域也称为静态作用域,它决定了变量的作用范围是基于代码的物理结构。
   JavaScript使用词法作用域,这意味着嵌套函数可以访问其外部函数的变量。举个例子,以下是JavaScript中不同作用域的示例:

// 全局作用域
var globalVar = "I am a global variable";

function myFunction() {
    // 局部作用域
    var localVar = "I am a local variable";
    
    if (true) {
        // 块级作用域
        let blockVar = "I am a block-scoped variable";
        console.log(blockVar); // 可以访问
    }

    console.log(localVar); // 可以访问
    // console.log(blockVar); // 会报错,blockVar在此处不可访问
}

myFunction();
console.log(globalVar); // 可以访问
// console.log(localVar); // 会报错,localVar在此处不可访问

10、src 和 href 的区别是?

`src` 用于嵌入内容,指示浏览器加载并嵌入该资源,例如图像、脚本或内嵌框架。
`href`用于创建超链接,指示浏览器导航到指定的资源或引用外部资源,如样式表或页面链接。

这两者在功能和使用场景上有明显的不同,`src`是嵌入资源,而`href`是导航或引用资源。理解这些区别有助于正确使用它们,确保网页能够正确加载资源和导航。

1. `src`(source)属性
用途:指定嵌入资源的位置或路径。
应用元素:主要用于`<img>`、`<script>`、`<iframe>`等标签。
作用:将外部资源嵌入到当前文档中。例如,`<img>`标签使用`src`属性来指定图片的路径,`<script>`标签使用`src`属性来加载外部的JavaScript文件。

     <!-- 指定图片资源 -->
     <img src="image.jpg" alt="Example Image">

     <!-- 指定外部 JavaScript 文件 -->
     <script src="script.js"></script>

     <!-- 指定嵌入的 iframe 资源 -->
     <iframe src="https://www.example.com"></iframe>

2. `href`(hyperlink reference)属性:
用途:指定超链接的目标地址。
应用元素:主要用于`<a>`、`<link>`等标签。
作用:创建指向其他页面或资源的链接。例如,`<a>`标签使用`href`属性来指定链接的目标URL,`<link>`标签使用`href`属性来指定外部CSS文件的位置。

     <!-- 指定超链接目标 -->
     <a href="https://www.example.com">Visit Example</a>

     <!-- 指定外部 CSS 文件 -->
     <link rel="stylesheet" href="styles.css">
  • 42
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不熬夜的臭宝

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值