1.1w字 | 初中级前端 JavaScript 自测清单 - 2 上

简介: 1.1w字 | 初中级前端 JavaScript 自测清单 - 2 上


网络异常,图片无法展示
|

前言

《初中级前端 JavaScript 自测清单 - 1》部分中,和大家简单过了一遍 JavaScript 基础知识,没看过的朋友可以回顾一下😁

本系列文章是我在我们团队内部的“现代 JavaScript 突击队”,第一期学习内容为《现代 JavaScript 教程》系列的第二部分输出内容,希望这份自测清单,能够帮助大家巩固知识,温故知新。

本部分内容,以 JavaScript 对象为主,大致包括以下内容:

网络异常,图片无法展示
|

一、对象

JavaScript 有八种数据额类型,有七种原始类型,它们值只包含一种类型(字符串,数字或其他),而对象是用来保存键值对和更复杂实体。 我们可以通过使用带有可选属性列表的花括号 **{...}** 来创建对象,一个属性就是一个键值对 {"key" : "value"} ,其中键( key )是一个字符串(或称属性名),值( value )可以是任何类型。

1. 创建对象

我们可以使用 2 种方式来创建一个新对象:

// 1. 通过“构造函数”创建
let user = new Object();
// 2. 通过“字面量”创建
let user = {};

2. 对象文本和属性

创建对象时,可以初始化对象的一些属性:

let user = {
  name : 'leo',
  age  : 18
}

然后可以对该对象进行属性对增删改查操作:

// 增加属性
user.addr = "China";
// user => {name: "leo", age: 18, addr: "China"}
// 删除属性
delete user.addr
// user => {name: "leo", age: 18}
// 修改属性
user.age  = 20;
// user => {name: "leo", age: 20}
// 查找属性
user.age;
// 20

3. 方括号的使用

当然对象的键( key )也可以是多词属性,但必须加引号,使用的时候,必须使用方括号( [] )读取:

let user = {
  name : 'leo',
  "my interest" : ["coding", "football", "cycling"]
}
user["my interest"]; // ["coding", "football", "cycling"]
delete user["my interest"];

我们也可以在方括号中使用变量,来获取属性值:

let key = "name";
let user = {
  name : "leo",
  age  : 18 
}
// ok
user[key]; // "leo"
user[key] = "pingan";
// error
user.key; // undefined

4. 计算属性

创建对象时,可以在对象字面量中使用方括号,即 计算属性

let key = "name";
let inputKey = prompt("请输入key", "age");
let user = {
  [key] : "leo",
  [inputKey] : 18
}
// 当用户在 prompt 上输入 "age" 时,user 变成下面样子:
// {name: "leo", age: 18}

当然,计算属性也可以是表达式:

let key = "name";
let user = {
  ["my_" + key] : "leo"
}
user["my_" + key]; // "leo"

5. 属性名简写

实际开发中,可以将相同的属性名和属性值简写成更短的语法:

// 原本书写方式
let getUser = function(name, age){
  // ...
  return {
    name: name,
    age: age
  }
}
// 简写方式
let getUser = function(name, age){
  // ...
  return {
    name,
    age
  }
}

也可以混用:

// 原本书写方式
let getUser = function(name, age){
  // ...
  return {
    name: name,
    age: 18
  }
}
// 简写方式
let getUser = function(name, age){
  // ...
  return {
    name,
    age: 18
  }
}

6. 对象属性存在性检测

6.1 使用 in 关键字

该方法可以判断对象的自有属性和继承来的属性是否存在。

let user = {name: "leo"};
"name" in user;            //true,自有属性存在
"age"  in user;            //false
"toString" in user;     //true,是一个继承属性

6.2使用对象的 hasOwnProperty() 方法。

该方法只能判断自有属性是否存在,对于继承属性会返回 false

let user = {name: "leo"};
user.hasOwnProperty("name");       //true,自有属性中有 name
user.hasOwnProperty("age");        //false,自有属性中不存在 age
user.hasOwnProperty("toString");   //false,这是一个继承属性,但不是自有属性

6.3 用 undefined 判断

该方法可以判断对象的自有属性和继承属性

let user = {name: "leo"};
user.name !== undefined;        // true
user.age  !== undefined;        // false
user.toString !== undefined     // true

该方法存在一个问题,如果属性的值就是 undefined  的话,该方法不能返回想要的结果:

let user = {name: undefined};
user.name !== undefined;        // false,属性存在,但值是undefined
user.age  !== undefined;        // false
user.toString !== undefined;    // true
复制代码

6.4 在条件语句中直接判断

let user = {};
if(user.name) user.name = "pingan";
//如果 name 是 undefined, null, false, " ", 0 或 NaN,它将保持不变
user; // {}

7. 对象循环遍历

当我们需要遍历对象中每一个属性,可以使用 for...in 语句来实现

7.1 for...in 循环

for...in 语句以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性。 注意for...in 不应该应用在一个数组,其中索引顺序很重要。

let user = {
  name : "leo",
  age  : 18
}
for(let k in user){
  console.log(k, user[k]);
}
// name leo
// age 18

7.2 ES7 新增方法

ES7中新增加的 Object.values()Object.entries()与之前的Object.keys()类似,返回数组类型。

1. Object.keys()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的健名。

let user = { name: "leo", age: 18};
Object.keys(user); // ["name", "age"]

2. Object.values()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。

let user = { name: "leo", age: 18};
Object.values(user); // ["leo", 18]

如果参数不是对象,则返回空数组:

Object.values(10);   // []
Object.values(true); // []

3. Object.entries()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。

let user = { name: "leo", age: 18};
Object.entries(user);
// [["name","leo"],["age",18]]

手动实现Object.entries()方法:

// Generator函数实现:  
function* entries(obj){
    for (let k of Object.keys(obj)){
        yield [k ,obj[k]];
    }
}
// 非Generator函数实现:
function entries (obj){
    let arr = [];
    for(let k of Object.keys(obj)){
        arr.push([k, obj[k]]);
    }
    return arr;
}

4. Object.getOwnPropertyNames(Obj)

该方法返回一个数组,它包含了对象 Obj 所有拥有的属性(无论是否可枚举)的名称。

let user = { name: "leo", age: 18};
Object.getOwnPropertyNames(user);
// ["name", "age"]

二、对象拷贝

参考文章《搞不懂JS中赋值·浅拷贝·深拷贝的请看这里》

1. 赋值操作

首先回顾下基本数据类型和引用数据类型:

  • 基本类型

概念:基本类型值在内存中占据固定大小,保存在栈内存中(不包含闭包中的变量)。 常见包括:undefined,null,Boolean,String,Number,Symbol

  • 引用类型

概念:引用类型的值是对象,保存在堆内存中。而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址(引用),引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。 常见包括:Object,Array,Date,Function,RegExp等

1.1 基本数据类型赋值

在栈内存中的数据发生数据变化的时候,系统会自动为新的变量分配一个新的之值在栈内存中,两个变量相互独立,互不影响的。

let user  = "leo";
let user1 = user;
user1 = "pingan";
console.log(user);  // "leo"
console.log(user1); // "pingan" 

1.2 引用数据类型赋值

在 JavaScript 中,变量不存储对象本身,而是存储其“内存中的地址”,换句话说就是存储对其的“引用”。 如下面 leo  变量只是保存对user 对象对应引用:

let user = { name: "leo", age: 18};
let leo  = user;

其他变量也可以引用 user 对象:

let leo1 = user;
let leo2 = user;

但是由于变量保存的是引用,所以当我们修改变量 leo \ leo1 \ leo2 这些值时,也会改动到引用对象user ,但当 user 修改,则其他引用该对象的变量,值都会发生变化:

leo.name = "pingan";
console.log(leo);   // {name: "pingan", age: 18}
console.log(leo1);  // {name: "pingan", age: 18}
console.log(leo2);  // {name: "pingan", age: 18}
console.log(user);  // {name: "pingan", age: 18}
user.name = "pingan8787";
console.log(leo);   // {name: "pingan8787", age: 18}
console.log(leo1);  // {name: "pingan8787", age: 18}
console.log(leo2);  // {name: "pingan8787", age: 18}
console.log(user);  // {name: "pingan8787", age: 18}

这个过程中涉及变量地址指针指向问题,这里暂时不展开讨论,有兴趣的朋友可以网上查阅相关资料。

2. 对象比较

当两个变量引用同一个对象时,它们无论是 == 还是 === 都会返回 true

let user = { name: "leo", age: 18};
let leo  = user;
let leo1 = user;
leo ==  leo1;   // true
leo === leo1;   // true
leo ==  user;   // true
leo === user;   // true

但如果两个变量是空对象 {} ,则不相等:

let leo1 = {};
let leo2 = {};
leo1 ==  leo2;  // false
leo1 === leo2;  // false

3. 浅拷贝

3.1 概念

概念:新的对象复制已有对象中非对象属性的值和对象属性的引用。也可以理解为:一个新的对象直接拷贝已存在的对象的对象属性的引用,即浅拷贝。

浅拷贝只对第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。

通过示例代码演示没有使用浅拷贝场景:

// 示例1 对象原始拷贝
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = user;
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1"
console.log(user.name);     // "leo1"
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90
// 示例2 数组原始拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = user;
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"
console.log(user[0]);         // "pingan888"
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

从上面示例代码可以看出: 由于对象被直接拷贝,相当于拷贝 引用数据类型 ,所以在新对象修改任何值时,都会改动到源数据。

接下来实现浅拷贝,对比以下。

3.2 实现浅拷贝

1. Object.assign()

语法: Object.assign(target, ...sources) ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标target,剩下的参数是拷贝的源对象sources(可以是多个)。 详细介绍,可以阅读文档《MDN Object.assign》

// 示例1 对象浅拷贝
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = Object.assign({}, user);
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90
// 示例2 数组浅拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = Object.assign({}, user);
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"  ⚠️ 差异!
console.log(user[0]);         // "leo"        ⚠️ 差异!
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

从打印结果可以看出,浅拷贝只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。

Object.assign() 使用注意:

  • 只拷贝源对象的自身属性(不拷贝继承属性);
  • 不会拷贝对象不可枚举的属性;
  • 属性名为Symbol 值的属性,可以被Object.assign拷贝;
  • undefinednull无法转成对象,它们不能作为Object.assign参数,但是可以作为源对象。
Object.assign(undefined); // 报错
Object.assign(null);      // 报错
Object.assign({}, undefined); // {}
Object.assign({}, null);      // {}
let user = {name: "leo"};
Object.assign(user, undefined) === user; // true
Object.assign(user, null)      === user; // true

2. Array.prototype.slice()

语法: arr.slice([begin[, end]])slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。 详细介绍,可以阅读文档《MDN Array slice》

// 示例 数组浅拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = Array.prototype.slice.call(user);
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"  ⚠️ 差异!
console.log(user[0]);         // "leo"        ⚠️ 差异!
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

3. Array.prototype.concat()

语法: var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。 详细介绍,可以阅读文档《MDN Array concat》

let user  = [{name: "leo"},   {age: 18}];
let user1 = [{age: 20},{addr: "fujian"}];
let user2 = user.concat(user1);
user1[0]["age"] = 25;
console.log(user);  // [{"name":"leo"},{"age":18}]
console.log(user1); // [{"age":25},{"addr":"fujian"}]
console.log(user2); // [{"name":"leo"},{"age":18},{"age":25},{"addr":"fujian"}]

Array.prototype.concat 也是一个浅拷贝,只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。

4. 拓展运算符(...)

语法: var cloneObj = { ...obj }; 扩展运算符也是浅拷贝,对于值是对象的属性无法完全拷贝成2个不同对象,但是如果属性都是基本类型的值的话,使用扩展运算符也是优势方便的地方。

let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = {...user};
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90

3.3 手写浅拷贝

实现原理:新的对象复制已有对象中非对象属性的值和对象属性的引用,也就是说对象属性并不复制到内存。

function cloneShallow(source) {
    let target = {};
    for (let key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
        }
    }
    return target;
}
  • for in

for...in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。

  • hasOwnProperty

该函数返回值为布尔值,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法,和 in 运算符不同,该函数会忽略掉那些从原型链上继承到的属性和自身属性。 语法:obj.hasOwnProperty(prop)prop 是要检测的属性字符串名称或者Symbol

4. 深拷贝

4.1 概念

复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象与原来的对象完全隔离,互不影响,对一个对象的修改并不会影响另一个对象。

4.2 实现深拷贝

1. JSON.parse(JSON.stringify())

其原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse() 反序列化将JSON字符串变成一个新的对象。

let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = JSON.parse(JSON.stringify(user));
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90 ⚠️ 差异!
console.log(user.skill.CSS);// 80 ⚠️ 差异!

JSON.stringify() 使用注意:

  • 拷贝的对象的值中如果有函数, undefinedsymbol 则经过 JSON.stringify() `序列化后的JSON字符串中这个键值对会消失;
  • 无法拷贝不可枚举的属性,无法拷贝对象的原型链;
  • 拷贝 Date 引用类型会变成字符串;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaNInfinity-Infinity ,则序列化的结果会变成 null
  • 无法拷贝对象的循环应用(即 obj[key] = obj )。

2. 第三方库

4.3 手写深拷贝

核心思想是递归,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。 实现代码:

const isObject = obj => typeof obj === 'object' && obj != null;
function cloneDeep(source) {
    if (!isObject(source)) return source; // 非对象返回自身
    const target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

该方法缺陷: 遇到循环引用,会陷入一个循环的递归过程,从而导致爆栈。 其他写法,可以阅读《如何写出一个惊艳面试官的深拷贝?》

5. 小结

浅拷贝:将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。

深拷贝:复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象与原来的对象完全隔离,互不影响,对一个对象的修改并不会影响另一个对象。

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

网络异常,图片无法展示
|

三、垃圾回收机制(GC)

垃圾回收(Garbage Collection,缩写为GC)是一种自动的存储器管理机制。当某个程序占用的一部分内存空间不再被这个程序访问时,这个程序会借助垃圾回收算法向操作系统归还这部分内存空间。垃圾回收器可以减轻程序员的负担,也减少程序中的错误。垃圾回收最早起源于LISP语言。 目前许多语言如Smalltalk、Java、C#和D语言都支持垃圾回收器,我们熟知的 JavaScript 具有自动垃圾回收机制。

在 JavaScript 中,原始类型的数据被分配到栈空间中,引用类型的数据会被分配到堆空间中。

1. 栈空间中的垃圾回收

当函数 showName 调用完成后,通过下移 ESP(Extended Stack Pointer)指针,来销毁 showName 函数,之后调用其他函数时,将覆盖掉旧内存,存放另一个函数的执行上下文,实现垃圾回收。

网络异常,图片无法展示
|
图片来自《浏览器工作原理与实践》

2. 堆空间中的垃圾回收

堆中数据垃圾回收策略的基础是:代际假说(The Generational Hypothesis)。即:

  1. 大部分对象在内存中存在时间极短,很多对象很快就不可访问。
  2. 不死的对象将活得更久。

这两个特点不仅仅适用于 JavaScript,同样适用于大多数的动态语言,如 Java、Python 等。 V8 引擎将堆空间分为新生代(存放生存时间短的对象)和老生代(存放生存时间长的对象)两个区域,并使用不同的垃圾回收器。

  • 副垃圾回收器,主要负责新生代的垃圾回收。
  • 主垃圾回收器,主要负责老生代的垃圾回收。

不管是哪种垃圾回收器,都使用相同垃圾回收流程:标记活动对象和非活动对象,回收非活动对象的内存,最后内存整理。 **

1.1 副垃圾回收器

使用 Scavenge 算法处理,将新生代空间对半分为两个区域,一个对象区域,一个空闲区域。  图片来自《浏览器工作原理与实践》

执行流程:

  • 新对象存在在对象区域,当对象区域将要写满时,执行一次垃圾回收;
  • 垃圾回收过程中,首先对对象区域中的垃圾做标记,然后副垃圾回收器将存活的对象复制并有序排列到空闲区域,相当于完成内存整理。
  • 复制完成后,将对象区域和空闲区域翻转,完成垃圾回收操作,这也让新生代中两块区域无限重复使用。

当然,这也存在一些问题:若复制操作的数据较大则影响清理效率。 JavaScript 引擎的解决方式是:将新生代区域设置得比较小,并采用对象晋升策略(经过两次回收仍存活的对象,会被移动到老生区),避免因为新生代区域较小引起存活对象装满整个区域的问题。

1.2 主垃圾回收器

分为:标记 - 清除(Mark-Sweep)算法,和标记 - 整理(Mark-Compact)算法

a)标记 - 清除(Mark-Sweep)算法过程:

  • 标记过程:从一组根元素开始遍历整个元素,能到达的元素为活动对象,反之为垃圾数据;
  • 清除过程:清理被标记的数据,并产生大量碎片内存。(缺点:导致大对象无法分配到足够的连续内存)

图片来自《浏览器工作原理与实践》

b)标记 - 整理(Mark-Compact)算法过程:

  • 标记过程:从一组根元素开始遍历整个元素,能到达的元素为活动对象,反之为垃圾数据;
  • 整理过程:将所有存活的对象,向一段移动,然后清除端边界以外的内容。

图片来自《浏览器工作原理与实践》

3. 拓展阅读

1.《图解Java 垃圾回收机制》 2.《MDN 内存管理》

四、对象方法和 this

1. 对象方法

具体介绍可阅读 《MDN 方法的定义》 。 将作为对象属性的方法称为“对象方法”,如下面 user 对象的 say 方法:

let user = {};
let say = function(){console.log("hello!")};
user.say = say;  // 赋值到对象上
user.say(); // "hello!"

也可以使用更加简洁的方法:

let user = {
  say: function(){}
  // 简写为
  say (){console.log("hello!")}
  // ES8 async 方法
  async say (){/.../}
}
user.say();

当然对象方法的名称,还支持计算的属性名称作为方法名:

const hello = "Hello";
let user = {
  ['say' + hello](){console.log("hello!")}
}
user['say' + hello](); // "hello!"

另外需要注意的是:所有方法定义不是构造函数,如果您尝试实例化它们,将抛出TypeError

let user = {
  say(){};
}
new user.say; // TypeError: user.say is not a constructor

2. this

2.1 this 简介

当对象方法需要使用对象中的属性,可以使用 this 关键字:

let user = {
  name : 'leo',
  say(){ console.log(`hello ${this.name}`)}
}
user.say(); // "hello leo"

当代码 user.say() 执行过程中, this 指的是 user 对象。当然也可以直接使用变量名 user 来引用 say() 方法:

let user = {
  name : 'leo',
  say(){ console.log(`hello ${user.name}`)}
}
user.say(); // "hello leo"

但是这样并不安全,因为 user 对象可能赋值给另外一个变量,并且将其他值赋值给 user 对象,就可能导致报错:

let user = {
  name : 'leo',
  say(){ console.log(`hello ${user.name}`)}
}
let leo = user;
user = null;
leo.say(); // Uncaught TypeError: Cannot read property 'name' of null

但将  user.name  改成 this.name 代码便正常运行。

2.2 this 取值

this 的值是在 代码运行时计算出来 的,它的值取决于代码上下文:

let user = { name: "leo"};
let admin = {name: "pingan"};
let say = function (){
  console.log(`hello ${this.name}`)
};
user.fun = say;
admin.fun = say;
// 函数内部 this 是指“点符号前面”的对象
user.fun();     // "hello leo"
admin.fun();    // "hello pingan"
admin['fun'](); // "hello pingan"

规则:如果 obj.fun() 被调用,则 thisfun 函数调用期间是 obj ,所以上面的 this 先是 user ,然后是 admin

但是在全局环境中,无论是否开启严格模式, this 都指向全局对象

console.log(this == window); // true
let a = 10;
this.b = 10;
a === this.b; // true

2.3 箭头函数没有自己的 this

箭头函数比较特别,没有自己的 this ,如果有引用 this 的话,则指向外部正常函数,下面例子中, this 指向 user.say() 方法:

let user = {
  name : 'leo',
  say : () => {
    console.log(`hello ${this.name}`);
  },
  hello(){
    let fun = () => console.log(`hello ${this.name}`);
    fun();
  }
}
user.say();   // hello      => say() 外部函数是 window
user.hello(); // hello leo  => fun() 外部函数是 hello

2.4 call / apply / bind

详细可以阅读《js基础-关于call,apply,bind的一切》 。 当我们想把 this 值绑定到另一个环境中,就可以使用 call / apply / bind 方法实现:

var user = { name: 'leo' };
var name = 'pingan';
function fun(){
  return console.log(this.name); // this 的值取决于函数调用方式
}
fun();           // "pingan"
fun.call(user);  // "leo"
fun.apply(user); // "leo"

注意:这里的 var name = 'pingan'; 需要使用 var 来声明,使用 let 的话, window 上将没有 name 变量。

三者语法如下:

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)


目录
相关文章
|
11天前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
16天前
|
资源调度 前端开发 JavaScript
vite3+vue3 实现前端部署加密混淆 javascript-obfuscator
【11月更文挑战第10天】本文介绍了在 Vite 3 + Vue 3 项目中使用 `javascript-obfuscator` 实现前端代码加密混淆的详细步骤,包括安装依赖、创建混淆脚本、修改 `package.json` 脚本命令、构建项目并执行混淆,以及在 HTML 文件中引用混淆后的文件。通过这些步骤,可以有效提高代码的安全性。
|
24天前
|
设计模式 前端开发 JavaScript
揭秘!前端大牛们如何巧妙利用JavaScript,打造智能交互体验!
【10月更文挑战第30天】前端开发领域充满了无限可能与创意,JavaScript作为核心语言,凭借强大的功能和灵活性,成为打造智能交互体验的重要工具。本文介绍前端大牛如何利用JavaScript实现平滑滚动、复杂动画、实时数据更新和智能表单验证等效果,展示了JavaScript的多样性和强大能力。
39 4
|
22天前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
|
22天前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
29 0
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
|
27天前
|
JavaScript 前端开发 开发者
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第27天】在前端开发领域,Vue.js和Angular是两个备受瞩目的框架。本文对比了两者的优劣,Vue.js以轻量级和易上手著称,适合快速开发小型到中型项目;Angular则由Google支持,功能全面,适合大型企业级应用。选择时需考虑项目需求、团队熟悉度和长期维护等因素。
40 1
|
28天前
|
JavaScript 前端开发 API
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第26天】前端技术的飞速发展让开发者在构建用户界面时有了更多选择。本文对比了Vue.js和Angular两大框架,介绍了它们的特点和优劣,并给出了在实际项目中如何选择的建议。Vue.js轻量级、易上手,适合小型项目;Angular结构化、功能强大,适合大型项目。
25 1
|
1月前
|
前端开发 JavaScript UED
"前端小技巧大揭秘:JS如何将后台时间戳秒变亲切小时前、分钟前,让用户秒懂,提升互动体验!"
【10月更文挑战第23天】在Web开发中,将后台返回的时间戳转换为“小时前”、“分钟前”、“刚刚”等友好的时间描述是常见需求。本文介绍如何用JavaScript实现这一功能,通过计算当前时间和时间戳的差值,返回相应的描述,提升用户体验。
30 1
|
2月前
|
前端开发 JavaScript 安全
JavaScript前端开发技术
JavaScript(简称JS)是一种广泛使用的脚本语言,特别在前端开发领域,它几乎成为了网页开发的标配。从简单的表单验证到复杂的单页应用(SPA),JavaScript都扮演着不可或缺的角色。
26 3
|
19天前
|
前端开发 JavaScript 安全
vite3+vue3 实现前端部署加密混淆 javascript-obfuscator
【11月更文挑战第7天】本文介绍了在 Vite 3 + Vue 3 项目中使用 `javascript-obfuscator` 实现前端代码加密混淆的详细步骤。包括项目准备、安装 `javascript-obfuscator`、配置 Vite 构建以应用混淆,以及最终构建项目进行混淆。通过这些步骤,可以有效提升前端代码的安全性,防止被他人轻易分析和盗用。