面试必问之【对象和函数】篇

本文深入探讨JavaScript中的对象和函数,解析对象的属性、特性、创建方式及函数的调用方式,涵盖原型、属性特性、闭包、bind方法等关键概念。

对象

对象:多个属性的动态集合,所谓动态就是可以新增属性也可以删除属性。

创建对象

  1. 直接量
  2. new Object()
  3. Object.create()

说到对象就必须说到原型 一般来说每个数据都是有原型的,并且可以通过对象.prototype

var o1 = Object.create(null);  //不会继承任何东西,不能和“+”运算符一起正常工作
var o2 = Object.create(Object.prototype)
var o3 = new Object()
var o4 = {}
// o2,o3,o4 创建的结果是一样的
复制代码

属性特性

属性: 包括名字和值,属性名可以使包含空字符串在内的任意字符串,值可以是任意的js值。

每个属性都有自己的一些属性特性:

  1. 可写;
  2. 可枚举;
  3. 可配置;

可以通过Object.defineProperty()来设置这些属性特性的值

var p = {
    //x和y是普通的可读写的数据属性
    x: 1.0,
    y: 1.o,
    //r是可读写的存取器属性,它有getter和setter
    get r(){
        return Math.sqrt(this.X * this.x + this.y * this.y)
    },
    set r(newValue){
        var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y)
        var ratio = newvalue/oldvalue
        this.x *=ratio;
        this.y *=ratio;
    },
    //theta是只读存取器属性,它只有getter方法
    get theta(){
        return Math.atan2(this.y,this.x)
    }
    
}
复制代码

那么我们下面来详细聊聊属性特性:

  1. 数据属性

我们可以认为一个属性包含一个名字和4个特性,数据属性的4个特性分别是它的值(value),可写性(writable),可枚举性(enumerable)和可配置性。

var o = {};
//添加一个不可枚举的数据属性x,并赋值为1
Object.defineProperty(o,'x',{
    value: 1,
    writable: true,
    enumerable: false,
    configurable: true
})
//属性存在,但是不可枚举
o.x;     // => 1
Object.keys(o)  // => []
//需要特别补充的是不可以通过defineProperty来修改继承属性
复制代码
  1. 存取器属性

但是对于存取器属性不具有值(value)特性和可写性,他们的可写性是由setter方法存在与否来决定的,因此对于存储器属性的4个特性是读取(get),写入(set),可枚举和可配置

var o = {
    a: 1
};
//添加一个不可枚举的数据属性x,并赋值为1
Object.defineProperty(o,'x',{
    enumerable: false,
    configurable: true,
    get(){
        return this.a
    },
    set(val){
        this.a = val
    }
})
//属性存在,但是不可枚举
o.x;     // => 1
Object.keys(o)  // => ['a']
//需要特别补充的是
//1.不可以通过defineProperty来修改继承属性
//2. 如果需要同时创建或者修改多个属性时,可以使用Object.defineProperties()
复制代码

现在我们看到可以通过Object.defineProperty来修改属性属性和存取器属性,但是对于那些不允许创建或修改的属性来说,如果用Object.defineProperty()或者Object.defineProperties()则会抛出异常,比如给一个不可扩展的对象新增属性就会抛出类型错误异常

属性的查询和设置

可以通过.和方括号[]运算符来获取属性的值,假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x,---->一直往上找直到找到为止或者到null也找不到,这种一直往上找属性就构成了一个“链” 赋值操作需要注意的就是三点:假设给对象o的属性x赋值

  1. 如果o中继承自属性x,那么这个这个继承的属性就被新创建的同名属性覆盖
  2. 如果o继承自一个只读属性x,那么赋值操作是不允许的。也就是说这时候是无法同名覆盖的
  3. 如果o中没有属性x,并且o是不可扩展的,那么赋值会失败
删除属性
  1. delete 只能删除自有属性,不能删除继承属性
  2. delete 不能删除那些配置性为false的属性
  3. 通过var let等声明的 不能删除
检测属性
  1. in //可枚举的都可
  2. hasOwnPreperty() //对象的自有属性
  3. propertyIsEumerable() //自有属性且是可枚举的
枚举属性
  1. for in //所有可枚举的属性 包括继承的
  2. Object.keys() //所有可枚举的自身属性组成的数组
  3. Object.getOwnPropertyNames() //所有自有属性的数组,包括不可枚举的

对象特性

原型属性

这个属性来自原型, 怎么查询来自哪个原型:

  1. 在es3中:
o.constructor.prototype
复制代码
  1. 在es5中
Object.getPrototypeOf()
复制代码
类属性

es3和es5中都未提供设置设个属性的方法,并且只有一种间接的方法可以查询它,默认的toString()方法,

function classof(o){
    if(o === null){
        return 'Null';
    }
    if(o === undefined) return 'Undefined';
    return Object.prototype.toString.call(o).slice(8,-1)
}

classof([])  // => Array
classof(1)   // => Number
classof(function(){})   // => Function
复制代码
可扩展性

es5定义了用来查询和设置对象可扩展性的函数。通过将对象传入Object.esExtensible()来判断该对象是否可扩展,如果想转换为不可扩展 Object.preventExtensions() 需要注意的是这个操作是不可逆的,也就是说一旦将对象转换为不可扩展的,就无法再将其转换会可扩展的了。 另外要了解一下Object.seal()和Object.freeze(), vue文档中可是有说到Object.freeze()哦,不了解的同学赶紧查查吧!

// Object.seal()
//Object.isSealed()
// Object.freeze()
// Object.isFrozen()
复制代码

对象的深浅拷贝

  1. JSON.stringfy() JSON.parse()
  2. 递归拷贝

函数

函数声明式语句被提前,可以被在它定义之前出现的代码调用,但是以表达式定义的函数不能提前调用,变量的声明提前了,但是给变量赋值并不会提前。

函数调用

  1. 作为函数
  2. 作为方法
  3. 作为构造函数
  4. 通过它们的call()和apply()方法间接调用

任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参,call()方法使用它自有的实参列表作为函数的实参,apply()方法则要求以数组的形式传入参数。

这里要说明一点对于很多初学者会很迷惑下面这两种写法,这里解释一下,如果构造函数没有形参,js构造函数调用的语法是允许省略圆括号的。

//下面两种写法在没有参数的时候是一样的
var o1 = new Object();
var o2 = new Object;
复制代码
实参对象 arguments

一个对象,碰巧具有以数字为索引的属性 =》 也称为类数组

闭包

函数内部访问函数外部的变量

函数定义时的作用域链在函数执行时依然有效。

常见经典考题:

  1. 请写一个方法,例如fn()不可以传参也不可以定义全局变量,每调用一次 输出值加1
function fn() {
    let i = 0;
    return function() {
        console.log(i++)
    }
}
let fn1 = fn();
fn1();
fn1()

//或者像书上这样写
var fn1 = (function(){
    var counter = 0;
    return function() {
        return counter ++;
    };
})()
fn1();
fn1()
复制代码
  1. 在来看看另一个经典考题:
var scope = "global scope"
function checkscope() {
    var scope = "local scope";
    function f() {
        return scope
    }
    return f;
}

checkscope()  // 返回值是什么   答案是local scope 而不是global scope
复制代码

回想一下词法作用域的基本规则: js函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数f()定义在这个作用域链里,其中的scope一定是局部变量,不管在任何时候执行函数f(),这种绑定在执行f()时依然有效。(死死咬住不放的感觉)

bind

将一个对象添加方法

//模拟bind实现
function bind(f,o){
    if(f.bind) return f.bind(o);
    else return function(){
        return f.apply(o,arguments)
    }
}
复制代码

另外 bind常用在柯里化

var sum = function(x,y){
    return x+y
}
var succ = sum.bind(null,1);
succ(2)    // => 3  x绑定到1,并传入2作为实参y

function f(y,z) {
    return this.x + y +z
}
var g = f.bind({x:1},2)
g(3)  //6
复制代码

以上就是自己总结的一些对象和函数常问的问题,部分给出了答案!当然有一些不足和不完善之处,欢迎各位指出!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值