JS自学的总结

JS的三种对象

  • 本地对象: Native Object (ECMAScript 提供)

Objdect、Function、Number、String、Boolean、Array、Error、EvalError、SyntaxError、RangeError、ReferenceError、TypeError、URIError、Date、RegExp

  • 内置对象 (ECMAScript 提供)

Built-in Object 、Global(代指全局下的方法)、Math

  • 宿主对象 Host Object

宿主对象是 执行JS脚本的环境 提供的对象,基本是浏览器,所以又称为浏览器对象。因为不同浏览器提供的宿主对象可能存在不同,所以会导致存在兼容性问题。浏览器对象window(BOM)和 DOM

函数

函数也是一种对象类型,引用类型。

比如:test.name、test.length、test.prototype等

function test(a, b) {}
console.log(test.name) // test
console.log(test.length) // 2

对象--->有些属性我们无法访问的-->JS引擎内部固有的属性

[[scope]] 保存着该函数的作用域链

  1. 函数创建时,生成一个JS内部的隐式属性

  2. 函数存储作用域链的容器

    AO/GO

    AO: 函数执行期间的执行上下文

    GO: 全局的执行期的上下文

    函数执行完成之后,会将AO进行销毁。当函数重新执行时,就会重新创建新的AO对象。

    当函数被定义时 --> [[scope]]被创建 --> scope chain 作用域链已经存在 --> GO --> AO(函数执行前的预编译才会生成AO)

闭包

闭包:当内部函数以某种方式被外部变量引用时,就会产生闭包。闭包的产生就会导致原来的作用域不释放,过度的闭包会导致内存泄漏或者加载速度慢。

立即执行函数

自执行函数:自动执行,执行完成后释放。

立即执行函数:IIFE --> immediately invoked function expression

(function () {
    // 第一种写法
})()

(function () {
    // W3C建议
}())

()括号包起来的,无论括号内部是什么,这个整体都将变成表达式

var test1 = function(){
    
}() // 可以执行

function test () {
    
}() // 不能执行

只有表达式才会被执行符号执行


补充:
() //  会被当做表达式
(1, 2) // 输出2 ','逗号也是一种选择符,选择最后一位。

例如:

var a = (1, 2) // 2
var fn = (
    function test1() {
        return '1'
    }(),
    function test2() {
        return '2'
    }()
)
console.log(typeof fn) // string
// fn ---> 由于逗号选择符的存在,所以fn = test2() ---> fn = '2' ---> typeof '2' ---> string

一道笔试题:

var a = 10
if (function b(){}) {
    a += typeof(b)
}
console.log(a)


答案:'10undefined'
解析:(function b(){}) // 可以看做一个表达式--->因此肯定是true--->可以看成(function(){})--->又因为typeof(b)为undefined

new的操作

function Car() {
    this.color = 'red';
    this.brand = 'brand'
}
var car = new Car()

// 解析

function Car() {
    // 第一步 创建 this = {};
    this.color = 'red';
    this.brand = 'brand'
    // 第二部执行函数 this = {color: 'red', brand: 'brand'}
    // 第三步 将this的__proto__指向构造函数的prototype
    // 第四步 隐式的return this回去
}

// 在这里需要注意的是:自己也可以在构造函数内自己显式的return。但是需要注意return的类型
/* 比如:
 *  return 123 --> 仍然会return this
 *  return 'asd' ---> 仍然会return this
 *  return {} ---> 就会显式变成 {}
 *  return function(){} ---> 就会显式变成    function(){}
 *  总结:如果是显式return引用类型数据,则就会return该值,如果是基础类型,则仍返回this
 *
 */

JS的包装类

常见的几个包装类:new Number、new String、new Boolean

首先原始值(纯数字,true/false、字符串)是不存在属性和方法的。

列子:

var a = 123
a.length // undefined

// JS包装类的过程
var a = 123
a.length = 3
console.log(a.length) // undefined

解析:

  1. console.log(a.length)首先原始值a是不存在属性和方法的。
  2. 系统检测到你硬是要加上属性,因此系统隐式将该数据进行包装
  3. a.length = 3 ---> 转换成 new Number(a).length = 3
  4. 但是系统又发觉该属性没地方存储,所以又只能delete 该属性。
  5. 所以到最后查找a.length --> undefined

同理为什么string类型能找到length属性的:

var a = 'abc'
console.log(a.length)

/*
* 首先new String() --> 就会自带length属性
* 同理a是不存在属性和方法的
* a.length ---> new String(a).length
* 所以访问a.length 就相当于访问 new String(a).length ---> 所以可以查询出等于3
*/

ASCII码 和 UNICODE 码

ASCII码 ---> 表1:0 -> 127位 表2:128 -> 255位
在计算机内每个ASCII码的都占用1 byte(字节)

UNICODE --> 包含ASCII码 -->255位以后的每一个字符都占用2个字节

写出一个函数接受任意字符串,算出这个字符串占用的字节大小?

// 首先需要知道怎么去获取该字符在UNICODE中是第几位

var a = 'a'
a.charCodeAt(0) // 97

// 因此思路是这样的根据UNICODE中的位置去计算字符串所占用的字节

function stringSizeOf(str) {
    if (typeof str !== 'string') {
        throw new Error('请输入字符串')
    }
    let num = str.length
    for (let i = 0; i < num; i++) {
        const unicodeNum = str.charCodeAt(i)
        if (unicodeNum > 255) {
            // 如果code数大于255,则占用两个字节
            num ++ // 相当于再加上 1
        }
    }
    return num + 'B'
}
stringSizeOf('hello world 你好世界') // 20B

原型和原型链

function foo() {}
foo.prototype // {constructor: ƒ } 是个对象

那就是说:

prototype 是函数 foo 的一个属性,称其为函数 foo的原型。其原型也是一个对象。

function HandPhone() {
    this.brand = '小米';
    this.color = 'red'
}

HandPhone.rom = '64G'


var hand1 = new HandPhone()
var hand2 = new HandPhone()

console.log(hand1.rom) // 64G
console.log(hand2.rom) // 64G

结论:

  • 这个prototype是构造函数构造出来的对象的共同祖先
  • 所有被构造函数构造出来的对象都继承构造函数的原型prototype上属性和方法
  • prototype下的constructor是指向其构造函数本身
function HandPhone() {
    this.brand = '小米';
    this.color = 'red'
}

HandPhone.rom = '64G'


var hand1 = new HandPhone()

console.log(hand1) // 
// {
//    brand: "小米"
//    color: "red"
//    __proto__: Object
// }

结论:

  • 每个实例化的对象都会存在一个 __proto__
  • 每个对象都会存在其一个属性 __proto__
  • __proto__ 该属性指向其构造函数的原型 prototype
  • __proto__ 也是一个对象

几个测试题:

// 第一种
function Car() {}

Car.prototype.name = 'Benz'

var car = new Car()

Car.prototype.name = 'Mazda'

console.log(car.name) 

// 毫无疑问输出 Mazda


// 第二种

function Car() {}

Car.prototype.name = 'Benz'

var car = new Car()

Car.prototype = {
    name: 'Mazda'
}

console.log(car.name) 

// 输出了 Benz

// 第三种
function Car() {}

Car.prototype.name = 'Benz'

Car.prototype = {
    name: 'Mazda'
}

var car = new Car()

console.log(car.name) 

// 输出 Mazda

解析:

第一种:因为Car.prototype只是修改其内部的属性,因此car的proto还是指向同一个prototype。因此输出是 Mazda

第二种:因为Car.prototype是重写的,并且是在实例化之后重写的,但car在实例化之后的proto指向的是实例化之前的prototype,而实例化之后重写了prototype,会修改constructor内的prototype(也就说修改了构造函数的prototype),跟已经实例化后的prototype没有关系。因此输出 Benz。

第三种:跟第二种刚好相反,重写在前,实例化在后,所以proto指向的是同一个prototype


总结

名称定义:

prototype:原型

__proto__:原型链(原型之间的链接点)

从属关系

prototype:函数的一个属性(每个函数都存在一个原型) --> 其实就是个对象

__proto__:对象的一个属性 --> 也是一个对象

关系:对象的proto属性保存着该对象的构造函数的 prototype(原型)。原型链的顶端就是 Object.prototype

function Test() {
  this.a = 1
}
console.log(Test.prototype) // 每个函数都存在prototype(原型)

var test = new Test()
console.log(test.__proto__) // 每个对象都存在__proto__

// 关系
console.log(test.__proto__ === Test.prototype) // true

// 因为Test.prototype ==> 也是个对象
console.log(Test.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null ---> 原型链的顶层

Test.prototype.b = 3
Object.prototype.c = 5

/**
 * test = {
 *   a: 1,
 *   __proto__: {
 *    // test的构造函数 Test.prototype
 *    b: 3,
 *    __proto__: {
 *      //Test的构造函数 Object.prototype
 *      c: 5,
 *      __proto__: null
 *    }
 *   }
 * }
 */

console.log(test.a)
console.log(test.b)
console.log(test.c)


// Function Object 函数 对象 --> 两者之间的关系

// Test = new Function
console.log(Test.__proto__ === Function.prototype) // true

// 底层定义
console.log(Function.__proto__ === Function.prototype) // Function的原型链指向自己的原型prototype

// const obj = {} ---> const obj = new Object
console.log(typeof Object) // function
console.log(Object.__proto__ === Function.prototype)

// 间接的导出一个恒等式
console.log(Object.__proto__ === Function.__proto__)

// 判断是不是自身的属性

console.log(test.hasOwnProperty('a')) // true
console.log(test.hasOwnProperty('b')) // false
console.log(test.hasOwnProperty('c')) // false


console.log('a' in test) // true
console.log('b' in test) // true
console.log('c' in test) // true

注意的点


function Test() {}

Test.prototype.num = 1
Test.prototype.obj = {
    name: 'test',
    add: 0
}

var test = new Test()

console.log(test) // {}

// 第一种情况
test.obj.name = 'new test'
test.obj.add++
console.log(test) // {}
console.log(Test.prototype) // {num: 1, obj: {name: "new test", add: 1}}

// 第二种情况
test.num++
console.log(test) // {num: 1}
console.log(Test.prototype) // {num: 1, obj: {name: "test", add: 0}}

// 解析:test.num++ ==> 相当于转换成 test.num = test.num + 1 ==> 因此test.num被存储下来。

// 第三种情况
test.obj.newName = 'new Test'
console.log(test) // {}
console.log(Test.prototype) // {num: 1, obj: {name: "test", newName: "new Test", add: 0}}

从上述代码可以得出结论:

  • 实例化的对象可以修改或者添加构造函数的原型上的引用值类型的属性。
  • 实例化的对象不可以修改或者添加构造函数的原型上的原始值。如语句内有修改,则会在实例化对象添加相应属性和属性值,原型上的值不变动。

看下一题:

var obj1 = {num: 1}
console.log(obj1)
// 输出
{
    num: 1,
    __proto__: Object.prototype
}

var obj2 = Object.create(null)
obj2.num = 1
console.log(obj2)
// 输出
{
    num: 1 // 并没有__proto__属性
}

obj2.__pro__ = {name: 'test'}
console.log(obj2.name) // undefined
// 这种自造的__proto__失去了原型链的功能

结论:

  • 不是所有的对象都继承于 Object.prototype ==> Object.create(null) 就不继承 Object.prototype
  • 自造的 __proto__ 属性失去了原型链的功能

深拷贝和浅拷贝

浅拷贝:浅拷贝有两种,一种是直接简单的将一个对象赋值给另一个对象,两个指向同一个地址。另一种是简单的将一个对象内的属性赋值给另一个数组,但是属性内的引用值不做任何处理。


var obj1 = {
    age: 10,
    sex: "男",
    car: ["奔驰", "宝马", "特斯拉", "奥拓"]
};
//另一个对象
var obj2 = {};

//写一个函数,作用:把一个对象的属性复制到另一个对象中,浅拷贝
//把a对象中的所有的属性复制到对象b中
function shallowCopy(obj,targetObj){
    var targetObj = targetObj || {}
    for (let key in obj){
        if ( obj.hasOwnProperty(key) ) {
            targetObj[key] = obj[key];
        }
    }
}
// 第一种
shallowCopy(obj1, obj2);

// 第二种
var obj3 = obj1;
console.dir(obj3);

深拷贝:把一个对象的属性和方法一个个找出来,在另一个对象中开辟对应的空间,一个个存储到另一个对象中。


function deepClone(obj, targetObj) {
  var targetObj = targetObj || {}
  var str = '[object Array]'
  for(var key in obj) {
    var value = obj[key]
    if (obj.hasOwnproperty(key)) {
      if (typeof value === 'object' && value !== null) {
        // 判断是不是数组
        if (Object.prototype.toString.call(value) === str) {
          targetObj[key] = []
        } else {
          targetObj[key] = {}
        }
        deepClone(value, targetObj[key])
      } else {
        targetObj[key] = obj[key]
      }
    }
  }
  return targetObj
}

区别: 浅拷贝只是简单的复制,对对象里面的对象属性和数组属性只是复制了地址,并没有创建新的相同对象或者数组。而深拷贝是完完全全的复制一份,空间大小占用一样但是位置不同!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容