影子的知识库

影子的知识库

  • 知识库
  • GitHub

›JS 基础

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

本文主要是记录 Javascript 中关于变量与对象的内容

变量声明

  • 声明后没有赋值的,值为 undefined

  • 重复声明不会改变该变量,例如:

    var a = 7;
    console.log(a); // 7
    var a;
    console.log(a); // 7
    
  • 常用的非覆盖操作:

    var a = a || 7; // 如果 a 已经有值就直接使用,否则赋值 7
    

变量和引用

  • 和 Java 相似,变量可能指向基本类型和引用类型

  • 函数传参是值传递,如果传递是参数是引用类型,相当于把引用类型的引用地址传递给了参数(可以这么理解),对象并没有进行复制,所以函数内部对传递的对象型参数进行修改是对外部有效的,例如:

    var a = {x:1};
    function test(arg){
        arg.x++;
    }
    test(a);
    console.log(a.x); // 输出 2
    
  • 变量和属性可以认为是完全一样的,例如:

    var a = 3
    var b = {x:3};
    // 可以认为 a 和 b.x 的地位是一样的,只不过两者的名称不同
    
  • web 环境中,全局对象是 window,node 环境中,全局对象是 global,web 环境中,我们在函数外部声明的变量都属于全局对象,可以将 JS 理解成一棵树,全局对象就是树根,我们定义的变量都是挂载树根下的,然后变量可能也是个对象,它也有属性,这样一层一层的,就构造成了一颗全局对象树。

  • 局部变量是调用函数时隐式生成的对象的属性,我们调用一个函数,就隐式生成了一个 Call 对象。

  • 默认参数的惯用写法:

    function getDistance(pos) {
        pos = pos || { x: 0, y: 0, z: 0 }; // 如果没有收到参数 pos 的话,则使用默认值
        return Math.sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
    }
    

构造函数和 new 表达式 (ES 5)

构造函数是用于生成对象的函数,例如:

// 构造函数(类的定义)
function MyClass(x, y) {
    this.x = x;
    this.y = y;
}
var obj = new MyClass(3, 2);
console.log(obj.x, obj.y);
// 输出 3 2

从形式看,构造函数有以下特质:

  • 构造函数本身和普通的函数声明形式相同
  • 构造函数通过 new 表达式来调用
  • 调用构造函数的 new 表达式的值是(被新生成的)对象的引用
  • 通过 new 表达式调用的构造函数内的 this 引用了(被新生成的)对象

new 表达式的操作

  • 生成一个不具有特别的操作对象(类型是函数名称)。
  • 构造函数的 this 指向这个新生成的空对象
  • 将这个新生成对象的引用返回(函数作为构造函数进行调用的时候,最后会隐式 return this)
  • 如果构造函数内最后有 return 语句,那么有以下情况:
    • return 返回的是基本类型,或者只有 return 没有其它值,那么行为跟上述一致,最终构造函数返回了构造出的新对象。(构造函数会无视掉基本类型和空的 return)
    • return 返回的是引用类型,那么构造函数返回的是这个引用,而不是构造的新对象

this 引用规则

最外层代码中,this 引用的是全局对象(非模块化的情况下,在 node 中无法还原 this === global,在浏览器环境中有 this === window)

在函数内,this 引用根据 函数调用方式 具有不同的指向:

函数的调用方式this 引用的引用对象
构造函数调用构造的新对象
方法调用接收方对象,例如 obj.method()指向 obj
apply 或是 call 调用由 apply 或 call 的参数指定的对象
其他方式的调用全局对象

接收方对象的示例:

var obj = {
    x: 100,
    doit: function () {
        console.log('method is called.' + this.x);
        var innerObj = {
            x: 20,
            inner: function () {
                console.log('inner method is called.' + this.x);
            }
        }
        innerObj.inner();
    }
}
obj.doit();
// 输出:
// method is called.100
// inner method is called.20

进一步说明接收方的概念(以下示例是跑在 node 环境中,所以全局对象是 global):

global.x = 3; // 给全局对象添加属性 x = 3
var obj = {
    x: 100,
    doit: function () {
        console.log('method is called.' + this.x);
    }
}
obj.doit(); // 接收方是 obj,所以输出的是 obj.x = 100
var tmp = obj.doit;
tmp(); // 没有接收方,this 指向全局对象 global,所以输出的是 global.x = 3;

apply 与 call

通过 apply 与 call 调用的函数的 this 引用可以指向任意对象。也就是说,它们可以显式地指定接收对象,例如:

function f() {
    console.log(this.x);
}
var obj = { x: 4 };
f.apply(obj); // 指定此次调用中,f指向的接收对象是 obj,因此输出 obj.x = 4
f.call(obj); // 指定此次调用中,f指向的接收对象是 obj,因此输出 obj.x = 4
var obj = {
    x: 3,
    doit: function () {
        console.log('method is called.' + this.x);
    }
};
var obj2 = { x: 5 };
obj.doit.apply(obj2); // 指定此次调用中,f指向的接收对象是 obj2,因此输出 obj2.x = 5

两种方法的第一个参数都是接收方对象,两种方式的区别仅仅在于后续传递参数的方式,例如:

function f(a, b) {
    console.log('this.x = ' + this.x + ', a = ' + a + ', b = ' + b);
}
f.apply({ x: 4 }, [1, 2]); // apply 后续参数需要使用数组传递 [1,2]
f.call({ x: 4 }, 1, 2); // call 后续参数按照原来参数的顺序进行传递 1,2

原型链

记住以下 2 条即可:

  • JavaScript 对象有一个指向一个原型对象的引用(称之为 __proto__)。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型对象,以及该对象的原型对象的原型对象,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(一直找到 Object 到 null)。例如:

    var obj1 = {
        x: 1,
        name: function () {
            return 'obj1';
        }
    };
    var obj2 = {
        y: 2,
        name: function () {
            return 'obj2';
        }
    };
    var obj3 = {
        z: 3,
        name: function () {
            return 'obj3';
        },
        toString: function () {
            for (var x in this) {
                if (x !== 'name' && x !== 'toString') {
                    console.log('Object: ' + this.name() + ', ' + x + ': ' + this[x]);
                }
            }
        }
    };
    obj1.__proto__ = obj2;
    obj2.__proto__ = obj3;
    // 经过以上操作,现在 obj1 的原型对象是 obj2,obj2 的原型对象是 obj3
    // 因此 obj1 拥有 obj2 和 obj3 的属性 y 和 z,obj2 拥有 obj3 的属性 z
    obj1.toString();
    obj2.toString();
    obj3.toString();
    // 输出:
    // Object: obj1, x: 1
    // Object: obj1, y: 2
    // Object: obj1, z: 3
    // Object: obj2, y: 2
    // Object: obj2, z: 3
    // Object: obj3, z: 3
    

    注意:

    __proto__ 不是一个标准属性,但是许多 JS 环境都实现了该属性。

    从 ES 6 开始,我们可以通过 Object.getPrototypeOf(obj) 和 Object.setPrototypeOf(obj) 来访问一个对象的原型对象,这个等同于非标准实现的对象的 __proto__ 属性。

    为了避免混淆,本文就一直使用 __proto__ 来代表一个对象的原型对象。

    千万注意 __proto__ 和下文要讲的 prototype 属性是两个完全不同的属性

  • 被构造函数创建的实例对象的 ___proto__ 指向这个构造函数的 prototype 属性,例如:

    function a() { }
    /**
     * a 对象(函数也是个对象)的原型现在是这个 { x: 3 } 对象
     * 因此 a.x = 3
     */
    a.__proto__ = { x: 3 };
    
    a.prototype.x = 5; // 将 a 的 prototype 属性对象,添加一个属性 x = 5
    
    /**
     * 由构造函数(a 对象)生成的对象,它的原型对象等于构造函数对象的 prototype 属性
     * 也就是说 b.__proto__ === a.prototype 此时是成立的
     * 如果我调用 a.prototype = otherObj,直接修改指针指向的话
     * 那么 b.__proto__ === a.prototype 这个等式就不成立了
     */
    var b = new a();
    
    console.log(a.x); // a.x = a.__proto__.x = 3
    /**
     * b.x = b.__proto__.x = a.prototype.x = 5
     * 记住(b.__proto__ === a.prototype)
     */
    console.log(b.x); 
    

    也可以这么说:只有构造函数对象的 prototype 属性才是有意义的,普通对象的 prototype 属性没有其它的含义(暂时理解是这样),而所有对象(包括构造函数对象)的 ___proto__ 属性都指向了这个对象本身的原型对象。

Object.create 方法

MDN 文档:Object.create()

Javascript 官方一共有 3 种对象生成方法:

  • 对象字面量
  • new 表达式
  • Object.create

API:Object.create(proto[, propertiesObject])

作用:创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

例如:

var Proto = { x: 2, y: 3 };
var obj = Object.create(Proto); // 相当于 var obj = {}; obj.__proto__ = Proto
console.log(obj.x); // 输出 2

这个方法的第二个参数是一个关联数组,key 是新对象的属性名,值是属性描述符(描述这个属性的值、元属性的值),例如:

var Proto = { x: 2, y: 3 };
var obj = Object.create(Proto,
    {
        a: { value: 5, writable: true, enumerable: true, configurable: true },
        b: { value: 8, writable: true, enumerable: true, configurable: true }
    }
);
console.log(obj.a); // 输出 5
console.log(obj.b); // 输出 8
console.log(obj.x); // 输出 2
console.log(obj.y); // 输出 3
Last updated on 11/8/2020
← 语句 表达式 运算符函数 →
  • 变量声明
  • 变量和引用
  • 构造函数和 new 表达式 (ES 5)
  • new 表达式的操作
  • this 引用规则
  • apply 与 call
  • 原型链
  • Object.create 方法
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng